diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 62a0debc12..dfcb597d70 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -189,13 +189,13 @@ processChatCommand = \case atomically . writeTVar ff $ Just filesFolder' pure CRCmdOk APIGetChats withPCC -> CRApiChats <$> withUser (\user -> withStore $ \st -> getChatPreviews st user withPCC) - APIGetChat cType cId pagination -> withUser $ \user -> case cType of + APIGetChat (ChatRef cType cId) pagination -> withUser $ \user -> case cType of CTDirect -> CRApiChat . AChat SCTDirect <$> withStore (\st -> getDirectChat st user cId pagination) CTGroup -> CRApiChat . AChat SCTGroup <$> withStore (\st -> getGroupChat st user cId pagination) CTContactRequest -> pure $ chatCmdError "not implemented" CTContactConnection -> pure $ chatCmdError "not supported" APIGetChatItems _pagination -> pure $ chatCmdError "not implemented" - APISendMessage cType chatId file_ quotedItemId_ mc -> withUser $ \user@User {userId} -> withChatLock $ case cType of + APISendMessage (ChatRef cType chatId) file_ quotedItemId_ mc -> withUser $ \user@User {userId} -> withChatLock $ case cType of CTDirect -> do ct@Contact {localDisplayName = c} <- withStore $ \st -> getContact st userId chatId (fileInvitation_, ciFile_) <- unzipMaybe <$> setupSndFileTransfer ct @@ -277,9 +277,9 @@ processChatCommand = \case unzipMaybe :: Maybe (a, b) -> (Maybe a, Maybe b) unzipMaybe t = (fst <$> t, snd <$> t) -- TODO discontinue - APISendMessageQuote cType chatId quotedItemId mc -> - processChatCommand $ APISendMessage cType chatId Nothing (Just quotedItemId) mc - APIUpdateChatItem cType chatId itemId mc -> withUser $ \user@User {userId} -> withChatLock $ case cType of + APISendMessageQuote chatId quotedItemId mc -> + processChatCommand $ APISendMessage chatId Nothing (Just quotedItemId) mc + APIUpdateChatItem (ChatRef cType chatId) itemId mc -> withUser $ \user@User {userId} -> withChatLock $ case cType of CTDirect -> do (ct@Contact {contactId, localDisplayName = c}, ci) <- withStore $ \st -> (,) <$> getContact st userId chatId <*> getDirectChatItem st userId chatId itemId case ci of @@ -308,7 +308,7 @@ processChatCommand = \case CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate CTContactRequest -> pure $ chatCmdError "not supported" CTContactConnection -> pure $ chatCmdError "not supported" - APIDeleteChatItem cType chatId itemId mode -> withUser $ \user@User {userId} -> withChatLock $ case cType of + APIDeleteChatItem (ChatRef cType chatId) itemId mode -> withUser $ \user@User {userId} -> withChatLock $ case cType of CTDirect -> do (ct@Contact {localDisplayName = c}, CChatItem msgDir deletedItem@ChatItem {meta = CIMeta {itemSharedMsgId}, file}) <- withStore $ \st -> (,) <$> getContact st userId chatId <*> getDirectChatItem st userId chatId itemId case (mode, msgDir, itemSharedMsgId) of @@ -348,12 +348,12 @@ processChatCommand = \case cancelFiles userId [(fileId, AFS msgDirection fileStatus)] withFilesFolder $ \filesFolder -> deleteFiles filesFolder [filePath] - APIChatRead cType chatId fromToIds -> withChatLock $ case cType of + APIChatRead (ChatRef cType chatId) fromToIds -> withChatLock $ case cType of CTDirect -> withStore (\st -> updateDirectChatItemsRead st chatId fromToIds) $> CRCmdOk CTGroup -> withStore (\st -> updateGroupChatItemsRead st chatId fromToIds) $> CRCmdOk CTContactRequest -> pure $ chatCmdError "not supported" CTContactConnection -> pure $ chatCmdError "not supported" - APIDeleteChat cType chatId -> withUser $ \User {userId} -> case cType of + APIDeleteChat (ChatRef cType chatId) -> withUser $ \User {userId} -> case cType of CTDirect -> do ct@Contact {localDisplayName} <- withStore $ \st -> getContact st userId chatId withStore (\st -> getContactGroupNames st userId ct) >>= \case @@ -417,7 +417,7 @@ processChatCommand = \case connectViaContact userId adminContactReq profile DeleteContact cName -> withUser $ \User {userId} -> do contactId <- withStore $ \st -> getContactIdByName st userId cName - processChatCommand $ APIDeleteChat CTDirect contactId + processChatCommand $ APIDeleteChat (ChatRef CTDirect contactId) ListContacts -> withUser $ \user -> CRContactsList <$> withStore (`getUserContacts` user) CreateMyAddress -> withUser $ \User {userId} -> withChatLock . procCmd $ do (connId, cReq) <- withAgent (`createConnection` SCMContact) @@ -440,10 +440,10 @@ processChatCommand = \case RejectContact cName -> withUser $ \User {userId} -> do connReqId <- withStore $ \st -> getContactRequestIdByName st userId cName processChatCommand $ APIRejectContact connReqId - SendMessage cName msg -> withUser $ \User {userId} -> do - contactId <- withStore $ \st -> getContactIdByName st userId cName + SendMessage chatName msg -> withUser $ \user -> do + chatRef <- getChatRef user chatName let mc = MCText $ safeDecodeUtf8 msg - processChatCommand $ APISendMessage CTDirect contactId Nothing Nothing mc + processChatCommand $ APISendMessage chatRef Nothing Nothing mc SendMessageBroadcast msg -> withUser $ \user -> do contacts <- withStore (`getUserContacts` user) withChatLock . procCmd $ do @@ -461,16 +461,16 @@ processChatCommand = \case contactId <- withStore $ \st -> getContactIdByName st userId cName quotedItemId <- withStore $ \st -> getDirectChatItemIdByText st userId contactId msgDir (safeDecodeUtf8 quotedMsg) let mc = MCText $ safeDecodeUtf8 msg - processChatCommand $ APISendMessage CTDirect contactId Nothing (Just quotedItemId) mc - DeleteMessage cName deletedMsg -> withUser $ \User {userId} -> do - contactId <- withStore $ \st -> getContactIdByName st userId cName - deletedItemId <- withStore $ \st -> getDirectChatItemIdByText st userId contactId SMDSnd (safeDecodeUtf8 deletedMsg) - processChatCommand $ APIDeleteChatItem CTDirect contactId deletedItemId CIDMBroadcast - EditMessage cName editedMsg msg -> withUser $ \User {userId} -> do - contactId <- withStore $ \st -> getContactIdByName st userId cName - editedItemId <- withStore $ \st -> getDirectChatItemIdByText st userId contactId SMDSnd (safeDecodeUtf8 editedMsg) + processChatCommand $ APISendMessage (ChatRef CTDirect contactId) Nothing (Just quotedItemId) mc + DeleteMessage chatName deletedMsg -> withUser $ \user -> do + chatRef <- getChatRef user chatName + deletedItemId <- getSentChatItemIdByText user chatRef deletedMsg + processChatCommand $ APIDeleteChatItem chatRef deletedItemId CIDMBroadcast + EditMessage chatName editedMsg msg -> withUser $ \user -> do + chatRef <- getChatRef user chatName + editedItemId <- getSentChatItemIdByText user chatRef editedMsg let mc = MCText $ safeDecodeUtf8 msg - processChatCommand $ APIUpdateChatItem CTDirect contactId editedItemId mc + processChatCommand $ APIUpdateChatItem chatRef editedItemId mc NewGroup gProfile -> withUser $ \user -> do gVar <- asks idsDrg CRGroupCreated <$> withStore (\st -> createNewGroup st gVar user gProfile) @@ -542,27 +542,14 @@ processChatCommand = \case pure $ CRGroupDeletedUser gInfo ListMembers gName -> CRGroupMembers <$> withUser (\user -> withStore (\st -> getGroupByName st user gName)) ListGroups -> CRGroupsList <$> withUser (\user -> withStore (`getUserGroupDetails` user)) - SendGroupMessage gName msg -> withUser $ \user -> do - groupId <- withStore $ \st -> getGroupIdByName st user gName - let mc = MCText $ safeDecodeUtf8 msg - processChatCommand $ APISendMessage CTGroup groupId Nothing Nothing mc SendGroupMessageQuote gName cName quotedMsg msg -> withUser $ \user -> do groupId <- withStore $ \st -> getGroupIdByName st user gName quotedItemId <- withStore $ \st -> getGroupChatItemIdByText st user groupId cName (safeDecodeUtf8 quotedMsg) let mc = MCText $ safeDecodeUtf8 msg - processChatCommand $ APISendMessage CTGroup groupId Nothing (Just quotedItemId) mc - DeleteGroupMessage gName deletedMsg -> withUser $ \user@User {localDisplayName} -> do - groupId <- withStore $ \st -> getGroupIdByName st user gName - deletedItemId <- withStore $ \st -> getGroupChatItemIdByText st user groupId (Just localDisplayName) (safeDecodeUtf8 deletedMsg) - processChatCommand $ APIDeleteChatItem CTGroup groupId deletedItemId CIDMBroadcast - EditGroupMessage gName editedMsg msg -> withUser $ \user@User {localDisplayName} -> do - groupId <- withStore $ \st -> getGroupIdByName st user gName - editedItemId <- withStore $ \st -> getGroupChatItemIdByText st user groupId (Just localDisplayName) (safeDecodeUtf8 editedMsg) - let mc = MCText $ safeDecodeUtf8 msg - processChatCommand $ APIUpdateChatItem CTGroup groupId editedItemId mc - LastMessages (Just c@(ChatName cType _)) count -> do - chatId <- getChatId c - CRLastMessages . aChatItems . chat <$> (processChatCommand . APIGetChat cType chatId $ CPLast count) + processChatCommand $ APISendMessage (ChatRef CTGroup groupId) Nothing (Just quotedItemId) mc + LastMessages (Just chatName) count -> withUser $ \user -> do + chatRef <- getChatRef user chatName + CRLastMessages . aChatItems . chat <$> (processChatCommand . APIGetChat chatRef $ CPLast count) LastMessages Nothing _count -> pure $ chatCmdError "not implemented" -- old file protocol -- SendFile cName f -> withUser $ \User {userId} -> do @@ -618,7 +605,7 @@ processChatCommand = \case -- new file protocol SendGroupFileInv gName f -> withUser $ \user -> do groupId <- withStore $ \st -> getGroupIdByName st user gName - processChatCommand $ APISendMessage CTGroup groupId (Just f) Nothing (MCText "") + processChatCommand $ APISendMessage (ChatRef CTGroup groupId) (Just f) Nothing (MCText "") ReceiveFile fileId filePath_ -> withUser $ \user@User {userId} -> withChatLock . procCmd $ do ft <- withStore $ \st -> getRcvFileTransfer st userId fileId @@ -659,10 +646,16 @@ processChatCommand = \case -- use function below to make commands "synchronous" procCmd :: m ChatResponse -> m ChatResponse procCmd = id - getChatId :: ChatName -> m Int64 - getChatId (ChatName cType name) = withUser $ \user@User {userId} -> case cType of - CTDirect -> withStore $ \st -> getContactIdByName st userId name - CTGroup -> withStore $ \st -> getGroupIdByName st user name + getChatRef :: User -> ChatName -> m ChatRef + getChatRef user@User {userId} (ChatName cType name) = + ChatRef cType <$> case cType of + CTDirect -> withStore $ \st -> getContactIdByName st userId name + CTGroup -> withStore $ \st -> getGroupIdByName st user name + _ -> throwChatError $ CECommandError "not supported" + getSentChatItemIdByText :: User -> ChatRef -> ByteString -> m Int64 + getSentChatItemIdByText user@User {userId, localDisplayName} (ChatRef cType cId) msg = case cType of + CTDirect -> withStore $ \st -> getDirectChatItemIdByText st userId cId SMDSnd (safeDecodeUtf8 msg) + CTGroup -> withStore $ \st -> getGroupChatItemIdByText st user cId (Just localDisplayName) (safeDecodeUtf8 msg) _ -> throwChatError $ CECommandError "not supported" connectViaContact :: UserId -> ConnectionRequestUri 'CMContact -> Profile -> m ChatResponse connectViaContact userId cReq profile = withChatLock $ do @@ -1919,14 +1912,14 @@ chatCommandP = <|> "/_start" $> StartChat <|> "/_files_folder " *> (SetFilesFolder <$> filePath) <|> "/_get chats" *> (APIGetChats <$> (" pcc=on" $> True <|> " pcc=off" $> False <|> pure False)) - <|> "/_get chat " *> (APIGetChat <$> chatTypeP <*> A.decimal <* A.space <*> chatPaginationP) + <|> "/_get chat " *> (APIGetChat <$> chatRefP <* A.space <*> chatPaginationP) <|> "/_get items count=" *> (APIGetChatItems <$> A.decimal) - <|> "/_send " *> (APISendMessage <$> chatTypeP <*> A.decimal <*> optional filePathTagged <*> optional quotedItemIdTagged <* A.space <*> msgContentP) - <|> "/_send_quote " *> (APISendMessageQuote <$> chatTypeP <*> A.decimal <* A.space <*> A.decimal <* A.space <*> msgContentP) - <|> "/_update item " *> (APIUpdateChatItem <$> chatTypeP <*> A.decimal <* A.space <*> A.decimal <* A.space <*> msgContentP) - <|> "/_delete item " *> (APIDeleteChatItem <$> chatTypeP <*> A.decimal <* A.space <*> A.decimal <* A.space <*> ciDeleteMode) - <|> "/_read chat " *> (APIChatRead <$> chatTypeP <*> A.decimal <* A.space <*> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal))) - <|> "/_delete " *> (APIDeleteChat <$> chatTypeP <*> A.decimal) + <|> "/_send " *> (APISendMessage <$> chatRefP <*> optional filePathTagged <*> optional quotedItemIdTagged <* A.space <*> msgContentP) + <|> "/_send_quote " *> (APISendMessageQuote <$> chatRefP <* A.space <*> A.decimal <* A.space <*> msgContentP) + <|> "/_update item " *> (APIUpdateChatItem <$> chatRefP <* A.space <*> A.decimal <* A.space <*> msgContentP) + <|> "/_delete item " *> (APIDeleteChatItem <$> chatRefP <* A.space <*> A.decimal <* A.space <*> ciDeleteMode) + <|> "/_read chat " *> (APIChatRead <$> chatRefP <* A.space <*> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal))) + <|> "/_delete " *> (APIDeleteChat <$> chatRefP) <|> "/_accept " *> (APIAcceptContact <$> A.decimal) <|> "/_reject " *> (APIRejectContact <$> A.decimal) <|> "/_profile " *> (APIUpdateProfile <$> jsonP) @@ -1951,22 +1944,19 @@ chatCommandP = <|> ("/delete #" <|> "/d #") *> (DeleteGroup <$> displayName) <|> ("/members #" <|> "/members " <|> "/ms #" <|> "/ms ") *> (ListMembers <$> displayName) <|> ("/groups" <|> "/gs") $> ListGroups - <|> A.char '#' *> (SendGroupMessage <$> displayName <* A.space <*> A.takeByteString) <|> (">#" <|> "> #") *> (SendGroupMessageQuote <$> displayName <* A.space <*> pure Nothing <*> quotedMsg <*> A.takeByteString) <|> (">#" <|> "> #") *> (SendGroupMessageQuote <$> displayName <* A.space <* optional (A.char '@') <*> (Just <$> displayName) <* A.space <*> quotedMsg <*> A.takeByteString) - <|> ("\\#" <|> "\\ #") *> (DeleteGroupMessage <$> displayName <* A.space <*> A.takeByteString) - <|> ("!#" <|> "! #") *> (EditGroupMessage <$> displayName <* A.space <*> (quotedMsg <|> pure "") <*> A.takeByteString) <|> ("/contacts" <|> "/cs") $> ListContacts <|> ("/connect " <|> "/c ") *> (Connect <$> ((Just <$> strP) <|> A.takeByteString $> Nothing)) <|> ("/connect" <|> "/c") $> AddContact <|> ("/delete @" <|> "/delete " <|> "/d @" <|> "/d ") *> (DeleteContact <$> displayName) - <|> A.char '@' *> (SendMessage <$> displayName <* A.space <*> A.takeByteString) + <|> (SendMessage <$> chatNameP <* A.space <*> A.takeByteString) <|> (">@" <|> "> @") *> sendMsgQuote (AMsgDirection SMDRcv) <|> (">>@" <|> ">> @") *> sendMsgQuote (AMsgDirection SMDSnd) - <|> ("\\@" <|> "\\ @") *> (DeleteMessage <$> displayName <* A.space <*> A.takeByteString) - <|> ("!@" <|> "! @") *> (EditMessage <$> displayName <* A.space <*> (quotedMsg <|> pure "") <*> A.takeByteString) + <|> ("\\ " <|> "\\") *> (DeleteMessage <$> chatNameP <* A.space <*> A.takeByteString) + <|> ("! " <|> "!") *> (EditMessage <$> chatNameP <* A.space <*> (quotedMsg <|> pure "") <*> A.takeByteString) <|> "/feed " *> (SendMessageBroadcast <$> A.takeByteString) - <|> ("/tail" <|> "/t") *> (LastMessages <$> optional chatNameP <*> msgCountP) + <|> ("/tail" <|> "/t") *> (LastMessages <$> optional (A.space *> chatNameP) <*> msgCountP) <|> ("/file #" <|> "/f #") *> (SendGroupFile <$> displayName <* A.space <*> filePath) <|> ("/file_v2 #" <|> "/f_v2 #") *> (SendGroupFileInv <$> displayName <* A.space <*> filePath) <|> ("/file @" <|> "/file " <|> "/f @" <|> "/f ") *> (SendFile <$> displayName <* A.space <*> filePath) @@ -2034,7 +2024,8 @@ chatCommandP = <|> (" admin" $> GRAdmin) <|> (" member" $> GRMember) <|> pure GRAdmin - chatNameP = A.space *> (ChatName <$> chatTypeP <*> displayName) + chatNameP = ChatName <$> chatTypeP <*> displayName + chatRefP = ChatRef <$> chatTypeP <*> A.decimal msgCountP = A.space *> A.decimal <|> pure 10 adminContactReq :: ConnReqContact diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index e3ab2dce22..ed3b0fa741 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -97,14 +97,14 @@ data ChatCommand | StartChat | SetFilesFolder FilePath | APIGetChats {pendingConnections :: Bool} - | APIGetChat ChatType Int64 ChatPagination + | APIGetChat ChatRef ChatPagination | APIGetChatItems Int - | APISendMessage ChatType Int64 (Maybe FilePath) (Maybe ChatItemId) MsgContent - | APISendMessageQuote ChatType Int64 ChatItemId MsgContent -- TODO discontinue - | APIUpdateChatItem ChatType Int64 ChatItemId MsgContent - | APIDeleteChatItem ChatType Int64 ChatItemId CIDeleteMode - | APIChatRead ChatType Int64 (ChatItemId, ChatItemId) - | APIDeleteChat ChatType Int64 + | APISendMessage ChatRef (Maybe FilePath) (Maybe ChatItemId) MsgContent + | APISendMessageQuote ChatRef ChatItemId MsgContent -- TODO discontinue + | APIUpdateChatItem ChatRef ChatItemId MsgContent + | APIDeleteChatItem ChatRef ChatItemId CIDeleteMode + | APIChatRead ChatRef (ChatItemId, ChatItemId) + | APIDeleteChat ChatRef | APIAcceptContact Int64 | APIRejectContact Int64 | APIUpdateProfile Profile @@ -128,11 +128,11 @@ data ChatCommand | AddressAutoAccept Bool | AcceptContact ContactName | RejectContact ContactName - | SendMessage ContactName ByteString + | SendMessage ChatName ByteString | SendMessageQuote {contactName :: ContactName, msgDir :: AMsgDirection, quotedMsg :: ByteString, message :: ByteString} | SendMessageBroadcast ByteString - | DeleteMessage ContactName ByteString - | EditMessage {contactName :: ContactName, editedMsg :: ByteString, message :: ByteString} + | DeleteMessage ChatName ByteString + | EditMessage {chatName :: ChatName, editedMsg :: ByteString, message :: ByteString} | NewGroup GroupProfile | AddMember GroupName ContactName GroupMemberRole | JoinGroup GroupName @@ -142,10 +142,7 @@ data ChatCommand | DeleteGroup GroupName | ListMembers GroupName | ListGroups - | SendGroupMessage GroupName ByteString | SendGroupMessageQuote {groupName :: GroupName, contactName_ :: Maybe ContactName, quotedMsg :: ByteString, message :: ByteString} - | DeleteGroupMessage GroupName ByteString - | EditGroupMessage {groupName :: ContactName, editedMsg :: ByteString, message :: ByteString} | LastMessages (Maybe ChatName) Int | SendFile ContactName FilePath | SendFileInv ContactName FilePath diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 612032ca6c..108e208768 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -44,6 +44,9 @@ data ChatType = CTDirect | CTGroup | CTContactRequest | CTContactConnection data ChatName = ChatName ChatType Text deriving (Show) +data ChatRef = ChatRef ChatType Int64 + deriving (Show) + instance ToJSON ChatType where toJSON = J.genericToJSON . enumJSON $ dropPrefix "CT" toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CT" diff --git a/src/Simplex/Chat/Terminal/Input.hs b/src/Simplex/Chat/Terminal/Input.hs index 5e8f4bcd74..19de70c604 100644 --- a/src/Simplex/Chat/Terminal/Input.hs +++ b/src/Simplex/Chat/Terminal/Input.hs @@ -45,7 +45,6 @@ runInputLoop ct cc = forever $ do echo s = printToTerminal ct [plain s] isMessage = \case Right SendMessage {} -> True - Right SendGroupMessage {} -> True Right SendFile {} -> True Right SendFileInv {} -> True Right SendGroupFile {} -> True @@ -139,7 +138,6 @@ updateTermState ac tw (key, ms) ts@TerminalState {inputString = s, inputPosition Left _ -> inp Right cmd -> case cmd of SendMessage {} -> "! " <> inp - SendGroupMessage {} -> "! " <> inp SendMessageQuote {contactName, message} -> T.unpack $ "! @" <> contactName <> " " <> safeDecodeUtf8 message SendGroupMessageQuote {groupName, message} -> T.unpack $ "! #" <> groupName <> " " <> safeDecodeUtf8 message _ -> inp