diff --git a/apps/ios/Shared/Model/Shared/APITypes.swift b/apps/ios/Shared/Model/Shared/APITypes.swift index 3eb9f60e7..bec9aa4dd 100644 --- a/apps/ios/Shared/Model/Shared/APITypes.swift +++ b/apps/ios/Shared/Model/Shared/APITypes.swift @@ -50,6 +50,7 @@ enum ChatCommand { case let .setFilesFolder(filesFolder): return "/_files_folder \(filesFolder)" case .apiGetChats: return "/_get chats pcc=on" case let .apiGetChat(type, id): return "/_get chat \(ref(type, id)) count=100" + // TODO replace with /_send_v2 case let .apiSendMessage(type, id, file, quotedItemId, mc): switch (file, quotedItemId) { case (nil, nil): return "/_send \(ref(type, id)) \(mc.cmdString)" @@ -57,6 +58,8 @@ enum ChatCommand { case let (nil, .some(quotedItemId)): return "/_send \(ref(type, id)) quoted \(quotedItemId) \(mc.cmdString)" case let (.some(file), .some(quotedItemId)): return "/_send \(ref(type, id)) file \(file) quoted \(quotedItemId) \(mc.cmdString)" } +// case let .apiSendMessage(type, id, file, quotedItemId, mc): +// return "/_send_v2 \(ref(type, id)) \(try! jsonEncoder.encode(ComposedMessage(filePath: file, quotedItemId: quotedItemId, msgContent: mc)))" case let .apiUpdateChatItem(type, id, itemId, mc): return "/_update item \(ref(type, id)) \(itemId) \(mc.cmdString)" case let .apiDeleteChatItem(type, id, itemId, mode): return "/_delete item \(ref(type, id)) \(itemId) \(mode.rawValue)" case let .apiRegisterToken(token): return "/_ntf register apns \(token)" @@ -301,6 +304,12 @@ enum ChatResponse: Decodable, Error { private var noDetails: String { get { "\(responseType): no details" } } } +struct ComposedMessage: Encodable { + var filePath: String? + var quotedItemId: Int64? + var msgContent: MsgContent +} + private func decodeCJSON(_ cjson: UnsafePointer) -> T? { let s = String.init(cString: cjson) let d = s.data(using: .utf8)! diff --git a/apps/ios/Shared/Model/Shared/ChatTypes.swift b/apps/ios/Shared/Model/Shared/ChatTypes.swift index cb73fe8bf..adcfe61e4 100644 --- a/apps/ios/Shared/Model/Shared/ChatTypes.swift +++ b/apps/ios/Shared/Model/Shared/ChatTypes.swift @@ -689,12 +689,7 @@ enum MsgContent { get { switch self { case let .text(text): return "text \(text)" - case let .link(text: text, preview: preview): - return "json {\"type\":\"link\",\"text\":\(encodeJSON(text)),\"preview\":\(encodeJSON(preview))}" - case let .image(text: text, image: image): - return "json {\"type\":\"image\",\"text\":\(encodeJSON(text)),\"image\":\(encodeJSON(image))}" - case let .file(text): return "json {\"type\":\"file\",\"text\":\(encodeJSON(text))}" - default: return "" + default: return "json \(encodeJSON(self))" } } } @@ -704,7 +699,6 @@ enum MsgContent { case text case preview case image - case file } } @@ -739,6 +733,32 @@ extension MsgContent: Decodable { } } +extension MsgContent: Encodable { + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .text(text): + try container.encode("text", forKey: .type) + try container.encode(text, forKey: .text) + case let .link(text, preview): + try container.encode("link", forKey: .type) + try container.encode(text, forKey: .text) + try container.encode(preview, forKey: .preview) + case let .image(text, image): + try container.encode("image", forKey: .type) + try container.encode(text, forKey: .text) + try container.encode(image, forKey: .image) + case let .file(text): + try container.encode("file", forKey: .type) + try container.encode(text, forKey: .text) + // TODO use original JSON and type + case let .unknown(_, text): + try container.encode("text", forKey: .type) + try container.encode(text, forKey: .text) + } + } +} + struct FormattedText: Decodable { var text: String var format: Format? diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 6093f0e4c..4c9e07e33 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -200,7 +200,7 @@ processChatCommand = \case CTContactRequest -> pure $ chatCmdError "not implemented" CTContactConnection -> pure $ chatCmdError "not supported" APIGetChatItems _pagination -> pure $ chatCmdError "not implemented" - APISendMessage (ChatRef cType chatId) file_ quotedItemId_ mc -> withUser $ \user@User {userId} -> withChatLock $ case cType of + APISendMessage (ChatRef cType chatId) (ComposedMessage 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 @@ -287,7 +287,7 @@ processChatCommand = \case unzipMaybe t = (fst <$> t, snd <$> t) -- TODO discontinue APISendMessageQuote chatId quotedItemId mc -> - processChatCommand $ APISendMessage chatId Nothing (Just quotedItemId) mc + processChatCommand . APISendMessage chatId $ ComposedMessage 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 @@ -515,7 +515,7 @@ processChatCommand = \case SendMessage chatName msg -> withUser $ \user -> do chatRef <- getChatRef user chatName let mc = MCText $ safeDecodeUtf8 msg - processChatCommand $ APISendMessage chatRef Nothing Nothing mc + processChatCommand . APISendMessage chatRef $ ComposedMessage Nothing Nothing mc SendMessageBroadcast msg -> withUser $ \user -> do contacts <- withStore (`getUserContacts` user) withChatLock . procCmd $ do @@ -533,7 +533,7 @@ 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 (ChatRef CTDirect contactId) Nothing (Just quotedItemId) mc + processChatCommand . APISendMessage (ChatRef CTDirect contactId) $ ComposedMessage Nothing (Just quotedItemId) mc DeleteMessage chatName deletedMsg -> withUser $ \user -> do chatRef <- getChatRef user chatName deletedItemId <- getSentChatItemIdByText user chatRef deletedMsg @@ -618,7 +618,7 @@ processChatCommand = \case groupId <- withStore $ \st -> getGroupIdByName st user gName quotedItemId <- withStore $ \st -> getGroupChatItemIdByText st user groupId cName (safeDecodeUtf8 quotedMsg) let mc = MCText $ safeDecodeUtf8 msg - processChatCommand $ APISendMessage (ChatRef CTGroup groupId) Nothing (Just quotedItemId) mc + processChatCommand . APISendMessage (ChatRef CTGroup groupId) $ ComposedMessage Nothing (Just quotedItemId) mc LastMessages (Just chatName) count -> withUser $ \user -> do chatRef <- getChatRef user chatName CRLastMessages . aChatItems . chat <$> (processChatCommand . APIGetChat chatRef $ CPLast count) @@ -626,7 +626,7 @@ processChatCommand = \case CRLastMessages <$> getAllChatItems st user (CPLast count) SendFile chatName f -> withUser $ \user -> do chatRef <- getChatRef user chatName - processChatCommand $ APISendMessage chatRef (Just f) Nothing (MCFile "") + processChatCommand . APISendMessage chatRef $ ComposedMessage (Just f) Nothing (MCFile "") ReceiveFile fileId filePath_ -> withUser $ \user@User {userId} -> withChatLock . procCmd $ do ft <- withStore $ \st -> getRcvFileTransfer st userId fileId @@ -2097,7 +2097,8 @@ chatCommandP = <|> "/_get chats" *> (APIGetChats <$> (" pcc=on" $> True <|> " pcc=off" $> False <|> pure False)) <|> "/_get chat " *> (APIGetChat <$> chatRefP <* A.space <*> chatPaginationP) <|> "/_get items count=" *> (APIGetChatItems <$> A.decimal) - <|> "/_send " *> (APISendMessage <$> chatRefP <*> optional filePathTagged <*> optional quotedItemIdTagged <* A.space <*> msgContentP) + <|> "/_send " *> (APISendMessage <$> chatRefP <*> composedMsgP) + <|> "/_send_v2 " *> (APISendMessage <$> chatRefP <*> jsonP) <|> "/_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) @@ -2203,6 +2204,7 @@ chatCommandP = fullNameP name = do n <- (A.space *> A.takeByteString) <|> pure "" pure $ if B.null n then name else safeDecodeUtf8 n + composedMsgP = ComposedMessage <$> optional filePathTagged <*> optional quotedItemIdTagged <* A.space <*> msgContentP filePath = T.unpack . safeDecodeUtf8 <$> A.takeByteString filePathTagged = " file " *> (T.unpack . safeDecodeUtf8 <$> A.takeTill (== ' ')) quotedItemIdTagged = " quoted " *> A.decimal diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index d572783a3..440dc64e5 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -14,7 +14,7 @@ import Control.Monad.Except import Control.Monad.IO.Unlift import Control.Monad.Reader import Crypto.Random (ChaChaDRG) -import Data.Aeson (ToJSON) +import Data.Aeson (FromJSON, ToJSON) import qualified Data.Aeson as J import Data.ByteString.Char8 (ByteString) import Data.Int (Int64) @@ -103,7 +103,7 @@ data ChatCommand | APIGetChats {pendingConnections :: Bool} | APIGetChat ChatRef ChatPagination | APIGetChatItems Int - | APISendMessage ChatRef (Maybe FilePath) (Maybe ChatItemId) MsgContent + | APISendMessage ChatRef ComposedMessage | APISendMessageQuote ChatRef ChatItemId MsgContent -- TODO discontinue | APIUpdateChatItem ChatRef ChatItemId MsgContent | APIDeleteChatItem ChatRef ChatItemId CIDeleteMode @@ -298,6 +298,13 @@ instance ToJSON PendingSubStatus where toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True} +data ComposedMessage = ComposedMessage + { filePath :: Maybe FilePath, + quotedItemId :: Maybe ChatItemId, + msgContent :: MsgContent + } + deriving (Show, Generic, FromJSON) + data ChatError = ChatError {errorType :: ChatErrorType} | ChatErrorAgent {agentError :: AgentErrorType}