2021-07-04 18:42:24 +01:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
2022-01-11 08:50:44 +00:00
|
|
|
{-# LANGUAGE DeriveAnyClass #-}
|
2025-01-10 15:27:29 +04:00
|
|
|
{-# LANGUAGE DerivingStrategies #-}
|
2021-06-25 18:18:24 +01:00
|
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
2023-11-18 21:52:01 +04:00
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
2021-07-04 18:42:24 +01:00
|
|
|
{-# LANGUAGE GADTs #-}
|
2025-01-10 15:27:29 +04:00
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
2021-07-04 18:42:24 +01:00
|
|
|
{-# LANGUAGE KindSignatures #-}
|
|
|
|
{-# LANGUAGE LambdaCase #-}
|
|
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2024-03-06 16:02:19 +02:00
|
|
|
{-# LANGUAGE PatternSynonyms #-}
|
2022-01-11 08:50:44 +00:00
|
|
|
{-# LANGUAGE RankNTypes #-}
|
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
2022-10-14 13:06:33 +01:00
|
|
|
{-# LANGUAGE StandaloneDeriving #-}
|
2022-09-29 16:26:43 +01:00
|
|
|
{-# LANGUAGE StrictData #-}
|
2023-10-26 15:44:50 +01:00
|
|
|
{-# LANGUAGE TemplateHaskell #-}
|
2022-10-14 13:06:33 +01:00
|
|
|
{-# LANGUAGE TypeApplications #-}
|
2023-08-25 04:56:37 +08:00
|
|
|
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
|
|
|
|
|
2021-06-25 18:18:24 +01:00
|
|
|
module Simplex.Chat.Protocol where
|
|
|
|
|
2022-03-13 19:34:03 +00:00
|
|
|
import Control.Applicative ((<|>))
|
2022-01-11 08:50:44 +00:00
|
|
|
import Control.Monad ((<=<))
|
2023-11-18 21:52:01 +04:00
|
|
|
import Data.Aeson (FromJSON (..), ToJSON (..), (.:), (.:?), (.=))
|
2021-07-11 12:22:22 +01:00
|
|
|
import qualified Data.Aeson as J
|
2022-03-03 08:32:25 +00:00
|
|
|
import qualified Data.Aeson.Encoding as JE
|
2022-02-02 11:31:01 +00:00
|
|
|
import qualified Data.Aeson.KeyMap as JM
|
2023-10-26 15:44:50 +01:00
|
|
|
import qualified Data.Aeson.TH as JQ
|
2022-01-11 08:50:44 +00:00
|
|
|
import qualified Data.Aeson.Types as JT
|
2021-07-04 18:42:24 +01:00
|
|
|
import qualified Data.Attoparsec.ByteString.Char8 as A
|
2022-03-13 19:34:03 +00:00
|
|
|
import Data.ByteString.Char8 (ByteString)
|
2022-10-14 13:06:33 +01:00
|
|
|
import qualified Data.ByteString.Char8 as B
|
|
|
|
import Data.ByteString.Internal (c2w, w2c)
|
2021-07-11 12:22:22 +01:00
|
|
|
import qualified Data.ByteString.Lazy.Char8 as LB
|
2025-03-07 07:47:32 +00:00
|
|
|
import Data.Either (fromRight)
|
2024-07-30 22:59:47 +01:00
|
|
|
import Data.List.NonEmpty (NonEmpty (..))
|
2024-03-06 16:02:19 +02:00
|
|
|
import qualified Data.List.NonEmpty as L
|
2025-01-29 13:04:48 +00:00
|
|
|
import Data.Map.Strict (Map)
|
|
|
|
import qualified Data.Map.Strict as M
|
2024-07-30 22:59:47 +01:00
|
|
|
import Data.Maybe (fromMaybe, mapMaybe)
|
2023-05-17 01:22:00 +02:00
|
|
|
import Data.String
|
2021-06-25 18:18:24 +01:00
|
|
|
import Data.Text (Text)
|
2022-11-15 15:24:55 +04:00
|
|
|
import qualified Data.Text as T
|
2025-01-12 22:47:24 +00:00
|
|
|
import Data.Text.Encoding (decodeLatin1, encodeUtf8)
|
2022-03-13 19:34:03 +00:00
|
|
|
import Data.Time.Clock (UTCTime)
|
2022-10-14 13:06:33 +01:00
|
|
|
import Data.Type.Equality
|
|
|
|
import Data.Typeable (Typeable)
|
|
|
|
import Data.Word (Word32)
|
2022-05-02 17:06:49 +01:00
|
|
|
import Simplex.Chat.Call
|
2025-01-20 17:41:48 +04:00
|
|
|
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
|
2021-07-04 18:42:24 +01:00
|
|
|
import Simplex.Chat.Types
|
2024-12-05 18:32:00 +00:00
|
|
|
import Simplex.Chat.Types.Preferences
|
2024-04-04 20:41:56 +01:00
|
|
|
import Simplex.Chat.Types.Shared
|
2024-03-10 11:31:14 +00:00
|
|
|
import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion)
|
2025-03-19 07:16:31 +00:00
|
|
|
import Simplex.Messaging.Agent.Store.DB (fromTextField_)
|
2024-04-25 12:52:26 +03:00
|
|
|
import Simplex.Messaging.Compression (Compressed, compress1, decompress1)
|
2022-10-14 13:06:33 +01:00
|
|
|
import Simplex.Messaging.Encoding
|
2022-01-11 08:50:44 +00:00
|
|
|
import Simplex.Messaging.Encoding.String
|
2025-03-19 07:16:31 +00:00
|
|
|
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON)
|
2024-03-06 16:02:19 +02:00
|
|
|
import Simplex.Messaging.Protocol (MsgBody)
|
2024-06-25 09:51:55 +04:00
|
|
|
import Simplex.Messaging.Util (decodeJSON, eitherToMaybe, encodeJSON, safeDecodeUtf8, (<$?>))
|
2023-09-01 19:20:07 +04:00
|
|
|
import Simplex.Messaging.Version hiding (version)
|
|
|
|
|
2024-03-10 11:31:14 +00:00
|
|
|
-- Chat version history:
|
|
|
|
-- 1 - support chat versions in connections (9/1/2023)
|
|
|
|
-- 2 - create contacts for group members only via x.grp.direct.inv (9/16/2023)
|
|
|
|
-- 3 - faster joining via group links without creating contact (10/30/2023)
|
|
|
|
-- 4 - group message forwarding (11/18/2023)
|
|
|
|
-- 5 - batch sending messages (12/23/2023)
|
|
|
|
-- 6 - send group welcome message after history (12/29/2023)
|
|
|
|
-- 7 - update member profiles (1/15/2024)
|
2024-07-30 22:59:47 +01:00
|
|
|
-- 8 - compress messages and PQ e2e encryption (2024-03-08)
|
|
|
|
-- 9 - batch sending in direct connections (2024-07-24)
|
2024-12-02 14:01:23 +00:00
|
|
|
-- 10 - business chats (2024-11-29)
|
2024-12-05 18:32:00 +00:00
|
|
|
-- 11 - fix profile update in business chats (2024-12-05)
|
2025-01-08 09:42:26 +00:00
|
|
|
-- 12 - support sending and receiving content reports (2025-01-03)
|
2025-02-25 14:05:49 +04:00
|
|
|
-- 14 - support sending and receiving group join rejection (2025-02-24)
|
2025-04-02 07:57:18 +00:00
|
|
|
-- 15 - support specifying message scopes for group messages (2025-03-12)
|
2025-06-10 15:12:23 +00:00
|
|
|
-- 16 - support short link data (2025-06-10)
|
2024-03-10 11:31:14 +00:00
|
|
|
|
2023-12-24 13:27:51 +00:00
|
|
|
-- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig.
|
|
|
|
-- This indirection is needed for backward/forward compatibility testing.
|
|
|
|
-- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code.
|
2024-03-05 20:27:00 +04:00
|
|
|
currentChatVersion :: VersionChat
|
2025-06-10 15:12:23 +00:00
|
|
|
currentChatVersion = VersionChat 16
|
2023-09-01 19:20:07 +04:00
|
|
|
|
2023-12-24 13:27:51 +00:00
|
|
|
-- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above)
|
2024-04-22 20:46:48 +04:00
|
|
|
supportedChatVRange :: VersionRangeChat
|
|
|
|
supportedChatVRange = mkVersionRange initialChatVersion currentChatVersion
|
2024-03-07 17:39:09 +04:00
|
|
|
{-# INLINE supportedChatVRange #-}
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2024-03-08 13:36:09 +00:00
|
|
|
-- version range that supports skipping establishing direct connections in a group and establishing direct connection via x.grp.direct.inv
|
|
|
|
groupDirectInvVersion :: VersionChat
|
|
|
|
groupDirectInvVersion = VersionChat 2
|
2023-09-16 17:55:48 +04:00
|
|
|
|
2023-10-30 20:40:20 +04:00
|
|
|
-- version range that supports joining group via group link without creating direct contact
|
2024-03-08 13:36:09 +00:00
|
|
|
groupFastLinkJoinVersion :: VersionChat
|
|
|
|
groupFastLinkJoinVersion = VersionChat 3
|
2023-10-30 20:40:20 +04:00
|
|
|
|
2023-11-18 21:52:01 +04:00
|
|
|
-- version range that supports group forwarding
|
2024-03-08 13:36:09 +00:00
|
|
|
groupForwardVersion :: VersionChat
|
|
|
|
groupForwardVersion = VersionChat 4
|
2023-11-18 21:52:01 +04:00
|
|
|
|
2023-12-23 17:07:23 +04:00
|
|
|
-- version range that supports batch sending in groups
|
2024-03-08 13:36:09 +00:00
|
|
|
batchSendVersion :: VersionChat
|
|
|
|
batchSendVersion = VersionChat 5
|
2023-12-23 17:07:23 +04:00
|
|
|
|
2023-12-29 22:42:55 +04:00
|
|
|
-- version range that supports sending group welcome message in group history
|
2024-03-08 13:36:09 +00:00
|
|
|
groupHistoryIncludeWelcomeVersion :: VersionChat
|
|
|
|
groupHistoryIncludeWelcomeVersion = VersionChat 6
|
2023-12-29 22:42:55 +04:00
|
|
|
|
2024-01-15 19:56:11 +04:00
|
|
|
-- version range that supports sending member profile updates to groups
|
2024-03-08 13:36:09 +00:00
|
|
|
memberProfileUpdateVersion :: VersionChat
|
|
|
|
memberProfileUpdateVersion = VersionChat 7
|
2024-01-15 19:56:11 +04:00
|
|
|
|
2024-03-08 13:36:09 +00:00
|
|
|
-- version range that supports compressing messages and PQ e2e encryption
|
|
|
|
pqEncryptionCompressionVersion :: VersionChat
|
|
|
|
pqEncryptionCompressionVersion = VersionChat 8
|
2024-03-06 16:02:19 +02:00
|
|
|
|
2024-07-30 22:59:47 +01:00
|
|
|
-- version range that supports batch sending in direct connections, and forwarding batched messages in groups
|
|
|
|
batchSend2Version :: VersionChat
|
|
|
|
batchSend2Version = VersionChat 9
|
|
|
|
|
2024-12-02 14:01:23 +00:00
|
|
|
-- supports differentiating business chats when joining contact addresses
|
|
|
|
businessChatsVersion :: VersionChat
|
|
|
|
businessChatsVersion = VersionChat 10
|
|
|
|
|
2024-12-05 18:32:00 +00:00
|
|
|
-- support updating preferences in business chats (XGrpPrefs message)
|
|
|
|
businessChatPrefsVersion :: VersionChat
|
|
|
|
businessChatPrefsVersion = VersionChat 11
|
|
|
|
|
2025-01-04 18:33:27 +00:00
|
|
|
-- support sending and receiving content reports (MCReport message content)
|
|
|
|
contentReportsVersion :: VersionChat
|
|
|
|
contentReportsVersion = VersionChat 12
|
|
|
|
|
2025-02-25 14:05:49 +04:00
|
|
|
-- support sending and receiving group join rejection (XGrpLinkReject)
|
|
|
|
groupJoinRejectVersion :: VersionChat
|
|
|
|
groupJoinRejectVersion = VersionChat 14
|
|
|
|
|
2025-04-02 07:57:18 +00:00
|
|
|
-- support group knocking (MsgScope)
|
|
|
|
groupKnockingVersion :: VersionChat
|
|
|
|
groupKnockingVersion = VersionChat 15
|
|
|
|
|
2025-06-10 15:12:23 +00:00
|
|
|
-- support short link data in invitation, contact and group links
|
|
|
|
shortLinkDataVersion :: VersionChat
|
|
|
|
shortLinkDataVersion = VersionChat 16
|
|
|
|
|
2024-03-10 11:31:14 +00:00
|
|
|
agentToChatVersion :: VersionSMPA -> VersionChat
|
|
|
|
agentToChatVersion v
|
|
|
|
| v < pqdrSMPAgentVersion = initialChatVersion
|
|
|
|
| otherwise = pqEncryptionCompressionVersion
|
|
|
|
|
2022-01-26 16:18:27 +04:00
|
|
|
data ConnectionEntity
|
2022-02-02 11:31:01 +00:00
|
|
|
= RcvDirectMsgConnection {entityConnection :: Connection, contact :: Maybe Contact}
|
|
|
|
| RcvGroupMsgConnection {entityConnection :: Connection, groupInfo :: GroupInfo, groupMember :: GroupMember}
|
|
|
|
| SndFileConnection {entityConnection :: Connection, sndFileTransfer :: SndFileTransfer}
|
|
|
|
| RcvFileConnection {entityConnection :: Connection, rcvFileTransfer :: RcvFileTransfer}
|
|
|
|
| UserContactConnection {entityConnection :: Connection, userContact :: UserContact}
|
2023-10-26 15:44:50 +01:00
|
|
|
deriving (Eq, Show)
|
2023-10-05 21:49:20 +03:00
|
|
|
|
2023-10-26 15:44:50 +01:00
|
|
|
$(JQ.deriveJSON (sumTypeJSON fstToLower) ''ConnectionEntity)
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2024-10-11 18:37:38 +04:00
|
|
|
connEntityInfo :: ConnectionEntity -> String
|
|
|
|
connEntityInfo = \case
|
|
|
|
RcvDirectMsgConnection c ct_ -> ctInfo ct_ <> ", status: " <> show (connStatus c)
|
|
|
|
RcvGroupMsgConnection c g m -> mInfo g m <> ", status: " <> show (connStatus c)
|
|
|
|
SndFileConnection c _ft -> "snd file, status: " <> show (connStatus c)
|
|
|
|
RcvFileConnection c _ft -> "rcv file, status: " <> show (connStatus c)
|
|
|
|
UserContactConnection c _uc -> "user address, status: " <> show (connStatus c)
|
|
|
|
where
|
|
|
|
ctInfo = maybe "connection" $ \Contact {contactId} -> "contact " <> show contactId
|
|
|
|
mInfo GroupInfo {groupId} GroupMember {groupMemberId} = "group " <> show groupId <> ", member " <> show groupMemberId
|
|
|
|
|
2022-02-02 17:01:12 +00:00
|
|
|
updateEntityConnStatus :: ConnectionEntity -> ConnStatus -> ConnectionEntity
|
|
|
|
updateEntityConnStatus connEntity connStatus = case connEntity of
|
2023-11-03 18:15:07 +00:00
|
|
|
RcvDirectMsgConnection c ct_ -> RcvDirectMsgConnection (st c) ((\ct -> (ct :: Contact) {activeConn = Just $ st c}) <$> ct_)
|
2022-02-02 17:01:12 +00:00
|
|
|
RcvGroupMsgConnection c gInfo m@GroupMember {activeConn = c'} -> RcvGroupMsgConnection (st c) gInfo m {activeConn = st <$> c'}
|
|
|
|
SndFileConnection c ft -> SndFileConnection (st c) ft
|
|
|
|
RcvFileConnection c ft -> RcvFileConnection (st c) ft
|
|
|
|
UserContactConnection c uc -> UserContactConnection (st c) uc
|
|
|
|
where
|
|
|
|
st c = c {connStatus}
|
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
data MsgEncoding = Binary | Json
|
|
|
|
|
|
|
|
data SMsgEncoding (e :: MsgEncoding) where
|
|
|
|
SBinary :: SMsgEncoding 'Binary
|
|
|
|
SJson :: SMsgEncoding 'Json
|
|
|
|
|
|
|
|
deriving instance Show (SMsgEncoding e)
|
|
|
|
|
|
|
|
class MsgEncodingI (e :: MsgEncoding) where
|
|
|
|
encoding :: SMsgEncoding e
|
|
|
|
|
|
|
|
instance MsgEncodingI 'Binary where encoding = SBinary
|
|
|
|
|
|
|
|
instance MsgEncodingI 'Json where encoding = SJson
|
|
|
|
|
|
|
|
instance TestEquality SMsgEncoding where
|
|
|
|
testEquality SBinary SBinary = Just Refl
|
|
|
|
testEquality SJson SJson = Just Refl
|
|
|
|
testEquality _ _ = Nothing
|
|
|
|
|
|
|
|
checkEncoding :: forall t e e'. (MsgEncodingI e, MsgEncodingI e') => t e' -> Either String (t e)
|
|
|
|
checkEncoding x = case testEquality (encoding @e) (encoding @e') of
|
|
|
|
Just Refl -> Right x
|
|
|
|
Nothing -> Left "bad encoding"
|
|
|
|
|
|
|
|
data AppMessage (e :: MsgEncoding) where
|
|
|
|
AMJson :: AppMessageJson -> AppMessage 'Json
|
|
|
|
AMBinary :: AppMessageBinary -> AppMessage 'Binary
|
|
|
|
|
2022-01-11 08:50:44 +00:00
|
|
|
-- chat message is sent as JSON with these properties
|
2022-10-14 13:06:33 +01:00
|
|
|
data AppMessageJson = AppMessageJson
|
2023-09-01 19:20:07 +04:00
|
|
|
{ v :: Maybe ChatVersionRange,
|
|
|
|
msgId :: Maybe SharedMsgId,
|
2022-03-13 19:34:03 +00:00
|
|
|
event :: Text,
|
2022-01-11 08:50:44 +00:00
|
|
|
params :: J.Object
|
|
|
|
}
|
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
data AppMessageBinary = AppMessageBinary
|
|
|
|
{ msgId :: Maybe SharedMsgId,
|
|
|
|
tag :: Char,
|
|
|
|
body :: ByteString
|
|
|
|
}
|
|
|
|
|
|
|
|
instance StrEncoding AppMessageBinary where
|
|
|
|
strEncode AppMessageBinary {tag, msgId, body} = smpEncode (tag, msgId', Tail body)
|
|
|
|
where
|
|
|
|
msgId' = maybe B.empty (\(SharedMsgId mId') -> mId') msgId
|
|
|
|
strP = do
|
|
|
|
(tag, msgId', Tail body) <- smpP
|
|
|
|
let msgId = if B.null msgId' then Nothing else Just (SharedMsgId msgId')
|
|
|
|
pure AppMessageBinary {tag, msgId, body}
|
|
|
|
|
2025-04-02 07:57:18 +00:00
|
|
|
data MsgScope
|
|
|
|
= MSMember {memberId :: MemberId} -- Admins can use any member id; members can use only their own id
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
|
|
|
$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "MS") ''MsgScope)
|
|
|
|
|
2023-10-26 15:44:50 +01:00
|
|
|
$(JQ.deriveJSON defaultJSON ''AppMessageJson)
|
|
|
|
|
2022-03-16 13:20:47 +00:00
|
|
|
data MsgRef = MsgRef
|
|
|
|
{ msgId :: Maybe SharedMsgId,
|
|
|
|
sentAt :: UTCTime,
|
|
|
|
sent :: Bool,
|
|
|
|
memberId :: Maybe MemberId -- must be present in all group message references, both referencing sent and received
|
|
|
|
}
|
2023-10-26 15:44:50 +01:00
|
|
|
deriving (Eq, Show)
|
|
|
|
|
|
|
|
$(JQ.deriveJSON defaultJSON ''MsgRef)
|
|
|
|
|
|
|
|
data LinkPreview = LinkPreview {uri :: Text, title :: Text, description :: Text, image :: ImageData, content :: Maybe LinkContent}
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
|
|
|
data LinkContent = LCPage | LCImage | LCVideo {duration :: Maybe Int} | LCUnknown {tag :: Text, json :: J.Object}
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
2025-01-04 18:33:27 +00:00
|
|
|
data ReportReason = RRSpam | RRContent | RRCommunity | RRProfile | RROther | RRUnknown Text
|
2025-01-10 15:27:29 +04:00
|
|
|
deriving (Eq, Show)
|
2025-01-04 18:33:27 +00:00
|
|
|
|
2023-10-26 15:44:50 +01:00
|
|
|
$(pure [])
|
|
|
|
|
|
|
|
instance FromJSON LinkContent where
|
|
|
|
parseJSON v@(J.Object j) =
|
|
|
|
$(JQ.mkParseJSON (taggedObjectJSON $ dropPrefix "LC") ''LinkContent) v
|
|
|
|
<|> LCUnknown <$> j .: "type" <*> pure j
|
|
|
|
parseJSON invalid =
|
|
|
|
JT.prependFailure "bad LinkContent, " (JT.typeMismatch "Object" invalid)
|
2022-03-13 19:34:03 +00:00
|
|
|
|
2023-10-26 15:44:50 +01:00
|
|
|
instance ToJSON LinkContent where
|
|
|
|
toJSON = \case
|
|
|
|
LCUnknown _ j -> J.Object j
|
|
|
|
v -> $(JQ.mkToJSON (taggedObjectJSON $ dropPrefix "LC") ''LinkContent) v
|
|
|
|
toEncoding = \case
|
|
|
|
LCUnknown _ j -> JE.value $ J.Object j
|
|
|
|
v -> $(JQ.mkToEncoding (taggedObjectJSON $ dropPrefix "LC") ''LinkContent) v
|
2022-03-13 19:34:03 +00:00
|
|
|
|
2023-10-26 15:44:50 +01:00
|
|
|
$(JQ.deriveJSON defaultJSON ''LinkPreview)
|
2022-03-13 19:34:03 +00:00
|
|
|
|
2025-01-04 18:33:27 +00:00
|
|
|
instance StrEncoding ReportReason where
|
|
|
|
strEncode = \case
|
|
|
|
RRSpam -> "spam"
|
|
|
|
RRContent -> "content"
|
|
|
|
RRCommunity -> "community"
|
|
|
|
RRProfile -> "profile"
|
|
|
|
RROther -> "other"
|
|
|
|
RRUnknown t -> encodeUtf8 t
|
|
|
|
strP =
|
|
|
|
A.takeTill (== ' ') >>= \case
|
|
|
|
"spam" -> pure RRSpam
|
|
|
|
"content" -> pure RRContent
|
|
|
|
"community" -> pure RRCommunity
|
|
|
|
"profile" -> pure RRProfile
|
|
|
|
"other" -> pure RROther
|
2025-01-12 22:47:24 +00:00
|
|
|
t -> pure $ RRUnknown $ safeDecodeUtf8 t
|
2025-01-04 18:33:27 +00:00
|
|
|
|
|
|
|
instance FromJSON ReportReason where
|
|
|
|
parseJSON = strParseJSON "ReportReason"
|
|
|
|
|
|
|
|
instance ToJSON ReportReason where
|
|
|
|
toJSON = strToJSON
|
|
|
|
toEncoding = strToJEncoding
|
|
|
|
|
2023-09-01 19:20:07 +04:00
|
|
|
data ChatMessage e = ChatMessage
|
2024-03-05 20:27:00 +04:00
|
|
|
{ chatVRange :: VersionRangeChat,
|
2023-09-01 19:20:07 +04:00
|
|
|
msgId :: Maybe SharedMsgId,
|
|
|
|
chatMsgEvent :: ChatMsgEvent e
|
|
|
|
}
|
2022-01-11 08:50:44 +00:00
|
|
|
deriving (Eq, Show)
|
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
data AChatMessage = forall e. MsgEncodingI e => ACMsg (SMsgEncoding e) (ChatMessage e)
|
|
|
|
|
|
|
|
data ChatMsgEvent (e :: MsgEncoding) where
|
|
|
|
XMsgNew :: MsgContainer -> ChatMsgEvent 'Json
|
2023-03-09 11:01:22 +00:00
|
|
|
XMsgFileDescr :: {msgId :: SharedMsgId, fileDescr :: FileDescr} -> ChatMsgEvent 'Json
|
2025-04-02 07:57:18 +00:00
|
|
|
XMsgUpdate :: {msgId :: SharedMsgId, content :: MsgContent, mentions :: Map MemberName MsgMention, ttl :: Maybe Int, live :: Maybe Bool, scope :: Maybe MsgScope} -> ChatMsgEvent 'Json
|
2023-02-08 07:08:53 +00:00
|
|
|
XMsgDel :: SharedMsgId -> Maybe MemberId -> ChatMsgEvent 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XMsgDeleted :: ChatMsgEvent 'Json
|
2023-05-15 12:28:53 +02:00
|
|
|
XMsgReact :: {msgId :: SharedMsgId, memberId :: Maybe MemberId, reaction :: MsgReaction, add :: Bool} -> ChatMsgEvent 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XFile :: FileInvitation -> ChatMsgEvent 'Json -- TODO discontinue
|
|
|
|
XFileAcpt :: String -> ChatMsgEvent 'Json -- direct file protocol
|
|
|
|
XFileAcptInv :: SharedMsgId -> Maybe ConnReqInvitation -> String -> ChatMsgEvent 'Json
|
|
|
|
XFileCancel :: SharedMsgId -> ChatMsgEvent 'Json
|
|
|
|
XInfo :: Profile -> ChatMsgEvent 'Json
|
2025-06-23 14:42:00 +01:00
|
|
|
XContact :: {profile :: Profile, contactReqId :: Maybe XContactId, welcomeMsgId :: Maybe SharedMsgId, requestMsg :: Maybe (SharedMsgId, MsgContent)} -> ChatMsgEvent 'Json
|
2023-09-27 19:36:13 +04:00
|
|
|
XDirectDel :: ChatMsgEvent 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XGrpInv :: GroupInvitation -> ChatMsgEvent 'Json
|
|
|
|
XGrpAcpt :: MemberId -> ChatMsgEvent 'Json
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkInv :: GroupLinkInvitation -> ChatMsgEvent 'Json
|
2025-02-25 14:05:49 +04:00
|
|
|
XGrpLinkReject :: GroupLinkRejection -> ChatMsgEvent 'Json
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkMem :: Profile -> ChatMsgEvent 'Json
|
2025-04-28 06:28:40 +00:00
|
|
|
XGrpLinkAcpt :: GroupAcceptance -> GroupMemberRole -> MemberId -> ChatMsgEvent 'Json
|
2025-04-02 07:57:18 +00:00
|
|
|
XGrpMemNew :: MemberInfo -> Maybe MsgScope -> ChatMsgEvent 'Json
|
2024-01-19 17:57:04 +04:00
|
|
|
XGrpMemIntro :: MemberInfo -> Maybe MemberRestrictions -> ChatMsgEvent 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XGrpMemInv :: MemberId -> IntroInvitation -> ChatMsgEvent 'Json
|
|
|
|
XGrpMemFwd :: MemberInfo -> IntroInvitation -> ChatMsgEvent 'Json
|
|
|
|
XGrpMemInfo :: MemberId -> Profile -> ChatMsgEvent 'Json
|
|
|
|
XGrpMemRole :: MemberId -> GroupMemberRole -> ChatMsgEvent 'Json
|
2024-01-19 17:57:04 +04:00
|
|
|
XGrpMemRestrict :: MemberId -> MemberRestrictions -> ChatMsgEvent 'Json
|
2023-11-18 21:52:01 +04:00
|
|
|
XGrpMemCon :: MemberId -> ChatMsgEvent 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XGrpMemConAll :: MemberId -> ChatMsgEvent 'Json -- TODO not implemented
|
2025-03-07 07:47:32 +00:00
|
|
|
XGrpMemDel :: MemberId -> Bool -> ChatMsgEvent 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XGrpLeave :: ChatMsgEvent 'Json
|
|
|
|
XGrpDel :: ChatMsgEvent 'Json
|
|
|
|
XGrpInfo :: GroupProfile -> ChatMsgEvent 'Json
|
2024-12-05 18:32:00 +00:00
|
|
|
XGrpPrefs :: GroupPreferences -> ChatMsgEvent 'Json
|
2025-05-19 11:14:43 +01:00
|
|
|
XGrpDirectInv :: ConnReqInvitation -> Maybe MsgContent -> Maybe MsgScope -> ChatMsgEvent 'Json
|
2023-11-18 21:52:01 +04:00
|
|
|
XGrpMsgForward :: MemberId -> ChatMessage 'Json -> UTCTime -> ChatMsgEvent 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XInfoProbe :: Probe -> ChatMsgEvent 'Json
|
|
|
|
XInfoProbeCheck :: ProbeHash -> ChatMsgEvent 'Json
|
|
|
|
XInfoProbeOk :: Probe -> ChatMsgEvent 'Json
|
|
|
|
XCallInv :: CallId -> CallInvitation -> ChatMsgEvent 'Json
|
|
|
|
XCallOffer :: CallId -> CallOffer -> ChatMsgEvent 'Json
|
|
|
|
XCallAnswer :: CallId -> CallAnswer -> ChatMsgEvent 'Json
|
|
|
|
XCallExtra :: CallId -> CallExtraInfo -> ChatMsgEvent 'Json
|
|
|
|
XCallEnd :: CallId -> ChatMsgEvent 'Json
|
|
|
|
XOk :: ChatMsgEvent 'Json
|
|
|
|
XUnknown :: {event :: Text, params :: J.Object} -> ChatMsgEvent 'Json
|
|
|
|
BFileChunk :: SharedMsgId -> FileChunk -> ChatMsgEvent 'Binary
|
|
|
|
|
|
|
|
deriving instance Eq (ChatMsgEvent e)
|
|
|
|
|
|
|
|
deriving instance Show (ChatMsgEvent e)
|
|
|
|
|
|
|
|
data AChatMsgEvent = forall e. MsgEncodingI e => ACME (SMsgEncoding e) (ChatMsgEvent e)
|
|
|
|
|
|
|
|
deriving instance Show AChatMsgEvent
|
|
|
|
|
2023-11-18 21:52:01 +04:00
|
|
|
isForwardedGroupMsg :: ChatMsgEvent e -> Bool
|
|
|
|
isForwardedGroupMsg ev = case ev of
|
|
|
|
XMsgNew mc -> case mcExtMsgContent mc of
|
|
|
|
ExtMsgContent {file = Just FileInvitation {fileInline = Just _}} -> False
|
|
|
|
_ -> True
|
|
|
|
XMsgFileDescr _ _ -> True
|
|
|
|
XMsgUpdate {} -> True
|
|
|
|
XMsgDel _ _ -> True
|
|
|
|
XMsgReact {} -> True
|
|
|
|
XFileCancel _ -> True
|
|
|
|
XInfo _ -> True
|
2025-04-02 07:57:18 +00:00
|
|
|
XGrpMemNew {} -> True
|
2023-11-18 21:52:01 +04:00
|
|
|
XGrpMemRole {} -> True
|
2024-01-19 17:57:04 +04:00
|
|
|
XGrpMemRestrict {} -> True
|
2025-03-07 07:47:32 +00:00
|
|
|
XGrpMemDel {} -> True -- TODO there should be a special logic when deleting host member (e.g., host forwards it before deleting connections)
|
2023-11-18 21:52:01 +04:00
|
|
|
XGrpLeave -> True
|
|
|
|
XGrpDel -> True -- TODO there should be a special logic - host should forward before deleting connections
|
|
|
|
XGrpInfo _ -> True
|
2024-12-05 18:32:00 +00:00
|
|
|
XGrpPrefs _ -> True
|
2023-11-18 21:52:01 +04:00
|
|
|
_ -> False
|
|
|
|
|
|
|
|
forwardedGroupMsg :: forall e. MsgEncodingI e => ChatMessage e -> Maybe (ChatMessage 'Json)
|
|
|
|
forwardedGroupMsg msg@ChatMessage {chatMsgEvent} = case encoding @e of
|
|
|
|
SJson | isForwardedGroupMsg chatMsgEvent -> Just msg
|
|
|
|
_ -> Nothing
|
|
|
|
|
2024-07-30 22:59:47 +01:00
|
|
|
-- applied after checking forwardedGroupMsg and building list of group members to forward to, see Chat;
|
2025-02-05 09:40:42 +00:00
|
|
|
--
|
2024-07-30 22:59:47 +01:00
|
|
|
-- this filters out members if any of forwarded events in batch is an XGrpMemRestrict event referring to them,
|
|
|
|
-- but practically XGrpMemRestrict is not batched with other events so it wouldn't prevent forwarding of other events
|
2025-02-05 09:40:42 +00:00
|
|
|
-- to these members;
|
|
|
|
--
|
|
|
|
-- same for reports (MCReport) - they are not batched with other events, so we can safely filter out
|
|
|
|
-- members with role less than moderator when forwarding
|
2024-07-30 22:59:47 +01:00
|
|
|
forwardedToGroupMembers :: forall e. MsgEncodingI e => [GroupMember] -> NonEmpty (ChatMessage e) -> [GroupMember]
|
|
|
|
forwardedToGroupMembers ms forwardedMsgs =
|
2025-02-05 09:40:42 +00:00
|
|
|
filter forwardToMember ms
|
2024-07-30 22:59:47 +01:00
|
|
|
where
|
2025-02-05 09:40:42 +00:00
|
|
|
forwardToMember GroupMember {memberId, memberRole} =
|
|
|
|
(memberId `notElem` restrictMemberIds)
|
|
|
|
&& (not hasReport || memberRole >= GRModerator)
|
2024-07-30 22:59:47 +01:00
|
|
|
restrictMemberIds = mapMaybe restrictMemberId $ L.toList forwardedMsgs
|
|
|
|
restrictMemberId ChatMessage {chatMsgEvent} = case encoding @e of
|
|
|
|
SJson -> case chatMsgEvent of
|
|
|
|
XGrpMemRestrict mId _ -> Just mId
|
|
|
|
_ -> Nothing
|
|
|
|
_ -> Nothing
|
2025-02-10 09:06:16 +00:00
|
|
|
hasReport = any isReportEvent forwardedMsgs
|
|
|
|
isReportEvent ChatMessage {chatMsgEvent} = case encoding @e of
|
2025-02-05 09:40:42 +00:00
|
|
|
SJson -> case chatMsgEvent of
|
|
|
|
XMsgNew mc -> case mcExtMsgContent mc of
|
|
|
|
ExtMsgContent {content = MCReport {}} -> True
|
|
|
|
_ -> False
|
|
|
|
_ -> False
|
|
|
|
_ -> False
|
2024-01-19 17:57:04 +04:00
|
|
|
|
2023-05-17 01:22:00 +02:00
|
|
|
data MsgReaction = MREmoji {emoji :: MREmojiChar} | MRUnknown {tag :: Text, json :: J.Object}
|
|
|
|
deriving (Eq, Show)
|
2023-05-15 12:28:53 +02:00
|
|
|
|
2023-05-17 01:22:00 +02:00
|
|
|
emojiTag :: IsString a => a
|
|
|
|
emojiTag = "emoji"
|
2023-05-15 12:28:53 +02:00
|
|
|
|
2025-02-14 23:37:06 +00:00
|
|
|
knownReaction :: MsgReaction -> Either String MsgReaction
|
|
|
|
knownReaction = \case
|
|
|
|
r@MREmoji {} -> Right r
|
|
|
|
MRUnknown {} -> Left "unknown MsgReaction"
|
|
|
|
|
|
|
|
-- parseJSON for MsgReaction parses unknown emoji reactions as MRUnknown with type "emoji",
|
|
|
|
-- allowing to add new emojis in a backwards compatible way - UI shows them as ?
|
2023-05-15 12:28:53 +02:00
|
|
|
instance FromJSON MsgReaction where
|
2023-05-17 01:22:00 +02:00
|
|
|
parseJSON (J.Object v) = do
|
|
|
|
tag <- v .: "type"
|
|
|
|
if tag == emojiTag
|
|
|
|
then (MREmoji <$> v .: emojiTag) <|> pure (MRUnknown tag v)
|
|
|
|
else pure $ MRUnknown tag v
|
|
|
|
parseJSON invalid =
|
|
|
|
JT.prependFailure "bad MsgContent, " (JT.typeMismatch "Object" invalid)
|
|
|
|
|
|
|
|
instance ToJSON MsgReaction where
|
|
|
|
toJSON = \case
|
|
|
|
MRUnknown {json} -> J.Object json
|
|
|
|
MREmoji emoji -> J.object ["type" .= (emojiTag :: Text), emojiTag .= emoji]
|
|
|
|
toEncoding = \case
|
|
|
|
MRUnknown {json} -> JE.value $ J.Object json
|
|
|
|
MREmoji emoji -> J.pairs $ "type" .= (emojiTag :: Text) <> emojiTag .= emoji
|
2023-05-15 12:28:53 +02:00
|
|
|
|
|
|
|
instance ToField MsgReaction where
|
|
|
|
toField = toField . encodeJSON
|
|
|
|
|
|
|
|
instance FromField MsgReaction where
|
|
|
|
fromField = fromTextField_ decodeJSON
|
|
|
|
|
|
|
|
newtype MREmojiChar = MREmojiChar Char
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
|
|
|
instance ToJSON MREmojiChar where
|
|
|
|
toEncoding (MREmojiChar c) = J.toEncoding c
|
|
|
|
toJSON (MREmojiChar c) = J.toJSON c
|
|
|
|
|
|
|
|
instance FromJSON MREmojiChar where
|
|
|
|
parseJSON v = mrEmojiChar <$?> J.parseJSON v
|
|
|
|
|
|
|
|
mrEmojiChar :: Char -> Either String MREmojiChar
|
|
|
|
mrEmojiChar c
|
2025-01-08 09:42:26 +00:00
|
|
|
| c `elem` ("👍👎😀😂😢❤️🚀✅" :: String) = Right $ MREmojiChar c
|
2023-05-15 12:28:53 +02:00
|
|
|
| otherwise = Left "bad emoji"
|
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
data FileChunk = FileChunk {chunkNo :: Integer, chunkBytes :: ByteString} | FileChunkCancel
|
2021-07-04 18:42:24 +01:00
|
|
|
deriving (Eq, Show)
|
2021-06-25 18:18:24 +01:00
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
instance Encoding FileChunk where
|
|
|
|
smpEncode = \case
|
|
|
|
FileChunk {chunkNo, chunkBytes} -> smpEncode ('F', fromIntegral chunkNo :: Word32, Tail chunkBytes)
|
|
|
|
FileChunkCancel -> smpEncode 'C'
|
|
|
|
smpP =
|
|
|
|
smpP >>= \case
|
|
|
|
'F' -> do
|
|
|
|
chunkNo <- fromIntegral <$> smpP @Word32
|
|
|
|
Tail chunkBytes <- smpP
|
|
|
|
pure FileChunk {chunkNo, chunkBytes}
|
|
|
|
'C' -> pure FileChunkCancel
|
|
|
|
_ -> fail "bad FileChunk"
|
|
|
|
|
|
|
|
newtype InlineFileChunk = IFC {unIFC :: FileChunk}
|
|
|
|
|
|
|
|
instance Encoding InlineFileChunk where
|
|
|
|
smpEncode (IFC chunk) = case chunk of
|
|
|
|
FileChunk {chunkNo, chunkBytes} -> smpEncode (w2c $ fromIntegral chunkNo, Tail chunkBytes)
|
|
|
|
FileChunkCancel -> smpEncode '\NUL'
|
|
|
|
smpP = do
|
|
|
|
c <- A.anyChar
|
|
|
|
IFC <$> case c of
|
|
|
|
'\NUL' -> pure FileChunkCancel
|
|
|
|
_ -> do
|
|
|
|
Tail chunkBytes <- smpP
|
|
|
|
pure FileChunk {chunkNo = fromIntegral $ c2w c, chunkBytes}
|
|
|
|
|
2022-03-16 13:20:47 +00:00
|
|
|
data QuotedMsg = QuotedMsg {msgRef :: MsgRef, content :: MsgContent}
|
2023-10-26 15:44:50 +01:00
|
|
|
deriving (Eq, Show)
|
2022-03-13 19:34:03 +00:00
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
cmToQuotedMsg :: AChatMsgEvent -> Maybe QuotedMsg
|
2022-03-13 19:34:03 +00:00
|
|
|
cmToQuotedMsg = \case
|
2022-10-14 13:06:33 +01:00
|
|
|
ACME _ (XMsgNew (MCQuote quotedMsg _)) -> Just quotedMsg
|
2022-03-13 19:34:03 +00:00
|
|
|
_ -> Nothing
|
2021-07-04 18:42:24 +01:00
|
|
|
|
2025-06-20 11:54:21 +01:00
|
|
|
data MsgContentTag
|
|
|
|
= MCText_
|
|
|
|
| MCLink_
|
|
|
|
| MCImage_
|
|
|
|
| MCVideo_
|
|
|
|
| MCVoice_
|
|
|
|
| MCFile_
|
|
|
|
| MCReport_
|
|
|
|
| MCChat_
|
|
|
|
| MCUnknown_ Text
|
2025-01-08 09:42:26 +00:00
|
|
|
deriving (Eq, Show)
|
2022-03-13 19:34:03 +00:00
|
|
|
|
|
|
|
instance StrEncoding MsgContentTag where
|
2022-01-11 08:50:44 +00:00
|
|
|
strEncode = \case
|
|
|
|
MCText_ -> "text"
|
2022-04-04 19:51:49 +01:00
|
|
|
MCLink_ -> "link"
|
2022-04-06 13:21:06 +04:00
|
|
|
MCImage_ -> "image"
|
2023-03-09 11:01:22 +00:00
|
|
|
MCVideo_ -> "video"
|
2022-04-28 09:40:51 +04:00
|
|
|
MCFile_ -> "file"
|
2022-11-15 15:24:55 +04:00
|
|
|
MCVoice_ -> "voice"
|
2025-01-04 18:33:27 +00:00
|
|
|
MCReport_ -> "report"
|
2025-06-20 11:54:21 +01:00
|
|
|
MCChat_ -> "chat"
|
2022-03-13 19:34:03 +00:00
|
|
|
MCUnknown_ t -> encodeUtf8 t
|
2022-01-11 08:50:44 +00:00
|
|
|
strDecode = \case
|
|
|
|
"text" -> Right MCText_
|
2022-04-04 19:51:49 +01:00
|
|
|
"link" -> Right MCLink_
|
2022-04-06 13:21:06 +04:00
|
|
|
"image" -> Right MCImage_
|
2023-03-09 11:01:22 +00:00
|
|
|
"video" -> Right MCVideo_
|
2022-11-15 15:24:55 +04:00
|
|
|
"voice" -> Right MCVoice_
|
2022-04-28 09:40:51 +04:00
|
|
|
"file" -> Right MCFile_
|
2025-01-04 18:33:27 +00:00
|
|
|
"report" -> Right MCReport_
|
2025-06-20 11:54:21 +01:00
|
|
|
"chat" -> Right MCChat_
|
2022-03-13 19:34:03 +00:00
|
|
|
t -> Right . MCUnknown_ $ safeDecodeUtf8 t
|
2022-01-11 08:50:44 +00:00
|
|
|
strP = strDecode <$?> A.takeTill (== ' ')
|
2021-07-24 10:26:28 +01:00
|
|
|
|
2022-03-13 19:34:03 +00:00
|
|
|
instance FromJSON MsgContentTag where
|
2022-01-11 08:50:44 +00:00
|
|
|
parseJSON = strParseJSON "MsgContentType"
|
2021-07-04 18:42:24 +01:00
|
|
|
|
2022-03-13 19:34:03 +00:00
|
|
|
instance ToJSON MsgContentTag where
|
2022-01-11 08:50:44 +00:00
|
|
|
toJSON = strToJSON
|
|
|
|
toEncoding = strToJEncoding
|
2021-07-04 18:42:24 +01:00
|
|
|
|
2025-01-10 13:58:02 +00:00
|
|
|
instance ToField MsgContentTag where toField = toField . safeDecodeUtf8 . strEncode
|
2025-01-04 18:33:27 +00:00
|
|
|
|
2022-03-13 19:34:03 +00:00
|
|
|
data MsgContainer
|
2022-04-06 13:21:06 +04:00
|
|
|
= MCSimple ExtMsgContent
|
|
|
|
| MCQuote QuotedMsg ExtMsgContent
|
2025-01-08 09:42:26 +00:00
|
|
|
| MCComment MsgRef ExtMsgContent
|
2022-04-06 13:21:06 +04:00
|
|
|
| MCForward ExtMsgContent
|
2022-03-13 19:34:03 +00:00
|
|
|
deriving (Eq, Show)
|
|
|
|
|
2022-04-10 13:30:58 +04:00
|
|
|
mcExtMsgContent :: MsgContainer -> ExtMsgContent
|
|
|
|
mcExtMsgContent = \case
|
|
|
|
MCSimple c -> c
|
|
|
|
MCQuote _ c -> c
|
2025-01-08 09:42:26 +00:00
|
|
|
MCComment _ c -> c
|
2022-04-10 13:30:58 +04:00
|
|
|
MCForward c -> c
|
2022-03-13 19:34:03 +00:00
|
|
|
|
2025-01-29 13:04:48 +00:00
|
|
|
isMCForward :: MsgContainer -> Bool
|
|
|
|
isMCForward = \case
|
|
|
|
MCForward _ -> True
|
|
|
|
_ -> False
|
|
|
|
|
2022-03-13 19:34:03 +00:00
|
|
|
data MsgContent
|
2025-01-30 10:06:26 +00:00
|
|
|
= MCText {text :: Text}
|
2022-04-04 19:51:49 +01:00
|
|
|
| MCLink {text :: Text, preview :: LinkPreview}
|
2022-04-06 13:21:06 +04:00
|
|
|
| MCImage {text :: Text, image :: ImageData}
|
2023-03-09 11:01:22 +00:00
|
|
|
| MCVideo {text :: Text, image :: ImageData, duration :: Int}
|
2022-11-15 15:24:55 +04:00
|
|
|
| MCVoice {text :: Text, duration :: Int}
|
2025-01-30 10:06:26 +00:00
|
|
|
| MCFile {text :: Text}
|
2025-01-04 18:33:27 +00:00
|
|
|
| MCReport {text :: Text, reason :: ReportReason}
|
2025-06-20 11:54:21 +01:00
|
|
|
| MCChat {text :: Text, chatLink :: MsgChatLink}
|
2022-03-13 19:34:03 +00:00
|
|
|
| MCUnknown {tag :: Text, text :: Text, json :: J.Object}
|
2021-07-04 18:42:24 +01:00
|
|
|
deriving (Eq, Show)
|
|
|
|
|
2025-06-20 11:54:21 +01:00
|
|
|
data MsgChatLink
|
|
|
|
= MCLContact {connLink :: ShortLinkContact, profile :: Profile, business :: Bool}
|
|
|
|
| MCLInvitation {invLink :: ShortLinkInvitation, profile :: Profile}
|
|
|
|
| MCLGroup {connLink :: ShortLinkContact, groupProfile :: GroupProfile}
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
2022-01-24 16:07:17 +00:00
|
|
|
msgContentText :: MsgContent -> Text
|
|
|
|
msgContentText = \case
|
|
|
|
MCText t -> t
|
2022-04-04 19:51:49 +01:00
|
|
|
MCLink {text} -> text
|
2022-04-06 13:21:06 +04:00
|
|
|
MCImage {text} -> text
|
2023-03-09 11:01:22 +00:00
|
|
|
MCVideo {text} -> text
|
2022-11-15 15:24:55 +04:00
|
|
|
MCVoice {text, duration} ->
|
|
|
|
if T.null text then msg else msg <> "; " <> text
|
|
|
|
where
|
2022-11-15 15:56:38 +04:00
|
|
|
msg = "voice message " <> durationText duration
|
2022-04-28 09:40:51 +04:00
|
|
|
MCFile t -> t
|
2025-01-04 18:33:27 +00:00
|
|
|
MCReport {text, reason} ->
|
|
|
|
if T.null text then msg else msg <> ": " <> text
|
|
|
|
where
|
|
|
|
msg = "report " <> safeDecodeUtf8 (strEncode reason)
|
2025-06-20 11:54:21 +01:00
|
|
|
MCChat {text} -> text
|
2022-03-13 19:34:03 +00:00
|
|
|
MCUnknown {text} -> text
|
2022-01-24 16:07:17 +00:00
|
|
|
|
2022-11-15 15:24:55 +04:00
|
|
|
durationText :: Int -> Text
|
|
|
|
durationText duration =
|
|
|
|
let (mins, secs) = duration `divMod` 60 in T.pack $ "(" <> with0 mins <> ":" <> with0 secs <> ")"
|
|
|
|
where
|
|
|
|
with0 n
|
2022-11-15 15:56:38 +04:00
|
|
|
| n <= 9 = '0' : show n
|
2022-11-15 15:24:55 +04:00
|
|
|
| otherwise = show n
|
|
|
|
|
2023-12-23 17:07:23 +04:00
|
|
|
msgContentHasText :: MsgContent -> Bool
|
2025-01-10 15:27:29 +04:00
|
|
|
msgContentHasText =
|
|
|
|
not . T.null . \case
|
|
|
|
MCVoice {text} -> text
|
|
|
|
mc -> msgContentText mc
|
2023-12-23 17:07:23 +04:00
|
|
|
|
2022-11-26 22:39:56 +00:00
|
|
|
isVoice :: MsgContent -> Bool
|
|
|
|
isVoice = \case
|
|
|
|
MCVoice {} -> True
|
|
|
|
_ -> False
|
|
|
|
|
2025-02-10 09:06:16 +00:00
|
|
|
isReport :: MsgContent -> Bool
|
|
|
|
isReport = \case
|
|
|
|
MCReport {} -> True
|
|
|
|
_ -> False
|
|
|
|
|
2022-03-13 19:34:03 +00:00
|
|
|
msgContentTag :: MsgContent -> MsgContentTag
|
|
|
|
msgContentTag = \case
|
2022-01-11 08:50:44 +00:00
|
|
|
MCText _ -> MCText_
|
2022-04-04 19:51:49 +01:00
|
|
|
MCLink {} -> MCLink_
|
2022-04-06 13:21:06 +04:00
|
|
|
MCImage {} -> MCImage_
|
2023-03-09 11:01:22 +00:00
|
|
|
MCVideo {} -> MCVideo_
|
2022-11-15 15:24:55 +04:00
|
|
|
MCVoice {} -> MCVoice_
|
2022-04-28 09:40:51 +04:00
|
|
|
MCFile {} -> MCFile_
|
2025-01-04 18:33:27 +00:00
|
|
|
MCReport {} -> MCReport_
|
2025-06-20 11:54:21 +01:00
|
|
|
MCChat {} -> MCChat_
|
2022-03-13 19:34:03 +00:00
|
|
|
MCUnknown {tag} -> MCUnknown_ tag
|
|
|
|
|
2025-01-29 13:04:48 +00:00
|
|
|
data ExtMsgContent = ExtMsgContent
|
|
|
|
{ content :: MsgContent,
|
|
|
|
-- the key used in mentions is a locally (per message) unique display name of member.
|
|
|
|
-- Suffixes _1, _2 should be appended to make names locally unique.
|
|
|
|
-- It should be done in the UI, as they will be part of the text, and validated in the API.
|
2025-01-30 10:06:26 +00:00
|
|
|
mentions :: Map MemberName MsgMention,
|
2025-01-29 13:04:48 +00:00
|
|
|
file :: Maybe FileInvitation,
|
|
|
|
ttl :: Maybe Int,
|
2025-04-02 07:57:18 +00:00
|
|
|
live :: Maybe Bool,
|
|
|
|
scope :: Maybe MsgScope
|
2025-01-29 13:04:48 +00:00
|
|
|
}
|
2022-04-06 13:21:06 +04:00
|
|
|
deriving (Eq, Show)
|
|
|
|
|
2025-01-30 10:06:26 +00:00
|
|
|
data MsgMention = MsgMention {memberId :: MemberId}
|
2025-01-29 13:04:48 +00:00
|
|
|
deriving (Eq, Show)
|
|
|
|
|
2025-06-20 11:54:21 +01:00
|
|
|
$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "MCL") ''MsgChatLink)
|
|
|
|
|
2025-01-30 10:06:26 +00:00
|
|
|
$(JQ.deriveJSON defaultJSON ''MsgMention)
|
2025-01-29 13:04:48 +00:00
|
|
|
|
2023-10-26 15:44:50 +01:00
|
|
|
$(JQ.deriveJSON defaultJSON ''QuotedMsg)
|
|
|
|
|
2023-12-23 17:07:23 +04:00
|
|
|
-- this limit reserves space for metadata in forwarded messages
|
2024-09-16 18:05:09 +01:00
|
|
|
-- 15780 (limit used for fileChunkSize) - 161 (x.grp.msg.forward overhead) = 15619, - 16 for block encryption ("rounded" to 15602)
|
2024-03-11 02:54:55 +04:00
|
|
|
maxEncodedMsgLength :: Int
|
2024-09-16 18:05:09 +01:00
|
|
|
maxEncodedMsgLength = 15602
|
2024-03-06 16:02:19 +02:00
|
|
|
|
2024-03-11 02:54:55 +04:00
|
|
|
-- maxEncodedMsgLength - 2222, see e2eEncUserMsgLength in agent
|
|
|
|
maxCompressedMsgLength :: Int
|
2024-09-16 18:05:09 +01:00
|
|
|
maxCompressedMsgLength = 13380
|
2024-03-06 16:02:19 +02:00
|
|
|
|
2024-03-11 02:54:55 +04:00
|
|
|
-- maxEncodedMsgLength - delta between MSG and INFO + 100 (returned for forward overhead)
|
|
|
|
-- delta between MSG and INFO = e2eEncUserMsgLength (no PQ) - e2eEncConnInfoLength (no PQ) = 1008
|
|
|
|
maxEncodedInfoLength :: Int
|
2024-09-16 18:05:09 +01:00
|
|
|
maxEncodedInfoLength = 14694
|
2024-03-11 02:54:55 +04:00
|
|
|
|
|
|
|
maxCompressedInfoLength :: Int
|
2024-09-16 18:05:09 +01:00
|
|
|
maxCompressedInfoLength = 10968 -- maxEncodedInfoLength - 3726, see e2eEncConnInfoLength in agent
|
2023-12-23 17:07:23 +04:00
|
|
|
|
2024-01-15 10:46:13 +00:00
|
|
|
data EncodedChatMessage = ECMEncoded ByteString | ECMLarge
|
2023-12-23 17:07:23 +04:00
|
|
|
|
2024-03-11 02:54:55 +04:00
|
|
|
encodeChatMessage :: MsgEncodingI e => Int -> ChatMessage e -> EncodedChatMessage
|
|
|
|
encodeChatMessage maxSize msg = do
|
2023-12-23 17:07:23 +04:00
|
|
|
case chatToAppMessage msg of
|
|
|
|
AMJson m -> do
|
2024-01-15 10:46:13 +00:00
|
|
|
let body = LB.toStrict $ J.encode m
|
2024-03-11 02:54:55 +04:00
|
|
|
if B.length body > maxSize
|
2023-12-23 17:07:23 +04:00
|
|
|
then ECMLarge
|
|
|
|
else ECMEncoded body
|
2024-01-15 10:46:13 +00:00
|
|
|
AMBinary m -> ECMEncoded $ strEncode m
|
2023-12-23 17:07:23 +04:00
|
|
|
|
|
|
|
parseChatMessages :: ByteString -> [Either String AChatMessage]
|
|
|
|
parseChatMessages "" = [Left "empty string"]
|
|
|
|
parseChatMessages s = case B.head s of
|
|
|
|
'{' -> [ACMsg SJson <$> J.eitherDecodeStrict' s]
|
|
|
|
'[' -> case J.eitherDecodeStrict' s of
|
|
|
|
Right v -> map parseItem v
|
|
|
|
Left e -> [Left e]
|
2024-03-06 16:02:19 +02:00
|
|
|
'X' -> decodeCompressed (B.drop 1 s)
|
2023-12-23 17:07:23 +04:00
|
|
|
_ -> [ACMsg SBinary <$> (appBinaryToCM =<< strDecode s)]
|
|
|
|
where
|
|
|
|
parseItem :: J.Value -> Either String AChatMessage
|
|
|
|
parseItem v = ACMsg SJson <$> JT.parseEither parseJSON v
|
2024-03-06 16:02:19 +02:00
|
|
|
decodeCompressed :: ByteString -> [Either String AChatMessage]
|
|
|
|
decodeCompressed s' = case smpDecode s' of
|
|
|
|
Left e -> [Left e]
|
2024-04-25 12:52:26 +03:00
|
|
|
Right (compressed :: L.NonEmpty Compressed) -> concatMap (either (pure . Left) parseChatMessages . decompress1) compressed
|
2024-03-06 16:02:19 +02:00
|
|
|
|
2024-03-19 15:33:27 +02:00
|
|
|
compressedBatchMsgBody_ :: MsgBody -> ByteString
|
|
|
|
compressedBatchMsgBody_ = markCompressedBatch . smpEncode . (L.:| []) . compress1
|
2024-03-06 16:02:19 +02:00
|
|
|
|
|
|
|
markCompressedBatch :: ByteString -> ByteString
|
|
|
|
markCompressedBatch = B.cons 'X'
|
|
|
|
{-# INLINE markCompressedBatch #-}
|
2023-10-26 15:44:50 +01:00
|
|
|
|
2022-03-13 19:34:03 +00:00
|
|
|
parseMsgContainer :: J.Object -> JT.Parser MsgContainer
|
|
|
|
parseMsgContainer v =
|
|
|
|
MCQuote <$> v .: "quote" <*> mc
|
2025-01-08 09:42:26 +00:00
|
|
|
<|> MCComment <$> v .: "parent" <*> mc
|
2022-03-13 19:34:03 +00:00
|
|
|
<|> (v .: "forward" >>= \f -> (if f then MCForward else MCSimple) <$> mc)
|
2025-01-08 09:42:26 +00:00
|
|
|
-- The support for arbitrary object in "forward" property is added to allow
|
|
|
|
-- forward compatibility with forwards that include public group links.
|
2024-04-09 13:02:59 +01:00
|
|
|
<|> (MCForward <$> ((v .: "forward" :: JT.Parser J.Object) *> mc))
|
2022-03-13 19:34:03 +00:00
|
|
|
<|> MCSimple <$> mc
|
|
|
|
where
|
2025-01-29 13:04:48 +00:00
|
|
|
mc = do
|
|
|
|
content <- v .: "content"
|
|
|
|
file <- v .:? "file"
|
|
|
|
ttl <- v .:? "ttl"
|
|
|
|
live <- v .:? "live"
|
|
|
|
mentions <- fromMaybe M.empty <$> (v .:? "mentions")
|
2025-04-02 07:57:18 +00:00
|
|
|
scope <- v .:? "scope"
|
|
|
|
pure ExtMsgContent {content, mentions, file, ttl, live, scope}
|
2022-12-14 12:16:11 +00:00
|
|
|
|
2022-12-16 07:51:04 +00:00
|
|
|
extMsgContent :: MsgContent -> Maybe FileInvitation -> ExtMsgContent
|
2025-04-02 07:57:18 +00:00
|
|
|
extMsgContent mc file = ExtMsgContent mc M.empty file Nothing Nothing Nothing
|
2022-12-16 07:51:04 +00:00
|
|
|
|
|
|
|
justTrue :: Bool -> Maybe Bool
|
|
|
|
justTrue True = Just True
|
|
|
|
justTrue False = Nothing
|
2022-01-11 08:50:44 +00:00
|
|
|
|
|
|
|
instance FromJSON MsgContent where
|
2022-03-13 19:34:03 +00:00
|
|
|
parseJSON (J.Object v) =
|
2022-01-11 08:50:44 +00:00
|
|
|
v .: "type" >>= \case
|
|
|
|
MCText_ -> MCText <$> v .: "text"
|
2022-04-04 19:51:49 +01:00
|
|
|
MCLink_ -> do
|
|
|
|
text <- v .: "text"
|
|
|
|
preview <- v .: "preview"
|
|
|
|
pure MCLink {text, preview}
|
2022-04-06 13:21:06 +04:00
|
|
|
MCImage_ -> do
|
|
|
|
text <- v .: "text"
|
|
|
|
image <- v .: "image"
|
2023-03-09 11:01:22 +00:00
|
|
|
pure MCImage {text, image}
|
|
|
|
MCVideo_ -> do
|
|
|
|
text <- v .: "text"
|
|
|
|
image <- v .: "image"
|
|
|
|
duration <- v .: "duration"
|
|
|
|
pure MCVideo {text, image, duration}
|
2022-11-15 15:24:55 +04:00
|
|
|
MCVoice_ -> do
|
|
|
|
text <- v .: "text"
|
|
|
|
duration <- v .: "duration"
|
|
|
|
pure MCVoice {text, duration}
|
2022-04-28 09:40:51 +04:00
|
|
|
MCFile_ -> MCFile <$> v .: "text"
|
2025-01-04 18:33:27 +00:00
|
|
|
MCReport_ -> do
|
|
|
|
text <- v .: "text"
|
|
|
|
reason <- v .: "reason"
|
|
|
|
pure MCReport {text, reason}
|
2025-06-20 11:54:21 +01:00
|
|
|
MCChat_ -> do
|
|
|
|
text <- v .: "text"
|
|
|
|
chatLink <- v .: "chatLink"
|
|
|
|
pure MCChat {text, chatLink}
|
2022-03-13 19:34:03 +00:00
|
|
|
MCUnknown_ tag -> do
|
|
|
|
text <- fromMaybe unknownMsgType <$> v .:? "text"
|
|
|
|
pure MCUnknown {tag, text, json = v}
|
2022-01-11 08:50:44 +00:00
|
|
|
parseJSON invalid =
|
|
|
|
JT.prependFailure "bad MsgContent, " (JT.typeMismatch "Object" invalid)
|
|
|
|
|
|
|
|
unknownMsgType :: Text
|
|
|
|
unknownMsgType = "unknown message type"
|
|
|
|
|
2022-03-13 19:34:03 +00:00
|
|
|
msgContainerJSON :: MsgContainer -> J.Object
|
|
|
|
msgContainerJSON = \case
|
2022-12-14 12:16:11 +00:00
|
|
|
MCQuote qm mc -> o $ ("quote" .= qm) : msgContent mc
|
2025-01-08 09:42:26 +00:00
|
|
|
MCComment ref mc -> o $ ("parent" .= ref) : msgContent mc
|
2022-12-14 12:16:11 +00:00
|
|
|
MCForward mc -> o $ ("forward" .= True) : msgContent mc
|
|
|
|
MCSimple mc -> o $ msgContent mc
|
2022-04-06 13:21:06 +04:00
|
|
|
where
|
2022-12-14 12:16:11 +00:00
|
|
|
o = JM.fromList
|
2025-04-02 07:57:18 +00:00
|
|
|
msgContent ExtMsgContent {content, mentions, file, ttl, live, scope} =
|
|
|
|
("file" .=? file) $ ("ttl" .=? ttl) $ ("live" .=? live) $ ("mentions" .=? nonEmptyMap mentions) $ ("scope" .=? scope) ["content" .= content]
|
2025-01-29 13:04:48 +00:00
|
|
|
|
|
|
|
nonEmptyMap :: Map k v -> Maybe (Map k v)
|
|
|
|
nonEmptyMap m = if M.null m then Nothing else Just m
|
|
|
|
{-# INLINE nonEmptyMap #-}
|
2022-03-13 19:34:03 +00:00
|
|
|
|
2022-01-11 08:50:44 +00:00
|
|
|
instance ToJSON MsgContent where
|
2022-03-03 08:32:25 +00:00
|
|
|
toJSON = \case
|
2022-03-13 19:34:03 +00:00
|
|
|
MCUnknown {json} -> J.Object json
|
2022-03-03 08:32:25 +00:00
|
|
|
MCText t -> J.object ["type" .= MCText_, "text" .= t]
|
2022-04-04 19:51:49 +01:00
|
|
|
MCLink {text, preview} -> J.object ["type" .= MCLink_, "text" .= text, "preview" .= preview]
|
2022-04-06 13:21:06 +04:00
|
|
|
MCImage {text, image} -> J.object ["type" .= MCImage_, "text" .= text, "image" .= image]
|
2023-03-27 15:35:01 +01:00
|
|
|
MCVideo {text, image, duration} -> J.object ["type" .= MCVideo_, "text" .= text, "image" .= image, "duration" .= duration]
|
2022-11-15 15:24:55 +04:00
|
|
|
MCVoice {text, duration} -> J.object ["type" .= MCVoice_, "text" .= text, "duration" .= duration]
|
2022-04-28 09:40:51 +04:00
|
|
|
MCFile t -> J.object ["type" .= MCFile_, "text" .= t]
|
2025-01-04 18:33:27 +00:00
|
|
|
MCReport {text, reason} -> J.object ["type" .= MCReport_, "text" .= text, "reason" .= reason]
|
2025-06-20 11:54:21 +01:00
|
|
|
MCChat {text, chatLink} -> J.object ["type" .= MCChat_, "text" .= text, "chatLink" .= chatLink]
|
2022-03-03 08:32:25 +00:00
|
|
|
toEncoding = \case
|
2022-03-13 19:34:03 +00:00
|
|
|
MCUnknown {json} -> JE.value $ J.Object json
|
2022-03-03 08:32:25 +00:00
|
|
|
MCText t -> J.pairs $ "type" .= MCText_ <> "text" .= t
|
2022-04-04 19:51:49 +01:00
|
|
|
MCLink {text, preview} -> J.pairs $ "type" .= MCLink_ <> "text" .= text <> "preview" .= preview
|
2022-04-06 13:21:06 +04:00
|
|
|
MCImage {text, image} -> J.pairs $ "type" .= MCImage_ <> "text" .= text <> "image" .= image
|
2023-03-27 15:35:01 +01:00
|
|
|
MCVideo {text, image, duration} -> J.pairs $ "type" .= MCVideo_ <> "text" .= text <> "image" .= image <> "duration" .= duration
|
2022-11-15 15:24:55 +04:00
|
|
|
MCVoice {text, duration} -> J.pairs $ "type" .= MCVoice_ <> "text" .= text <> "duration" .= duration
|
2022-04-28 09:40:51 +04:00
|
|
|
MCFile t -> J.pairs $ "type" .= MCFile_ <> "text" .= t
|
2025-01-04 18:33:27 +00:00
|
|
|
MCReport {text, reason} -> J.pairs $ "type" .= MCReport_ <> "text" .= text <> "reason" .= reason
|
2025-06-20 11:54:21 +01:00
|
|
|
MCChat {text, chatLink} -> J.pairs $ "type" .= MCChat_ <> "text" .= text <> "chatLink" .= chatLink
|
2022-01-11 08:50:44 +00:00
|
|
|
|
2022-03-19 09:04:53 +00:00
|
|
|
instance ToField MsgContent where
|
2022-11-04 12:00:03 +04:00
|
|
|
toField = toField . encodeJSON
|
2022-03-13 19:34:03 +00:00
|
|
|
|
|
|
|
instance FromField MsgContent where
|
2022-11-04 12:00:03 +04:00
|
|
|
fromField = fromTextField_ decodeJSON
|
2022-03-13 19:34:03 +00:00
|
|
|
|
2023-10-26 15:44:50 +01:00
|
|
|
data ACMEventTag = forall e. MsgEncodingI e => ACMEventTag (SMsgEncoding e) (CMEventTag e)
|
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
data CMEventTag (e :: MsgEncoding) where
|
|
|
|
XMsgNew_ :: CMEventTag 'Json
|
2023-03-09 11:01:22 +00:00
|
|
|
XMsgFileDescr_ :: CMEventTag 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XMsgUpdate_ :: CMEventTag 'Json
|
|
|
|
XMsgDel_ :: CMEventTag 'Json
|
|
|
|
XMsgDeleted_ :: CMEventTag 'Json
|
2023-05-15 12:28:53 +02:00
|
|
|
XMsgReact_ :: CMEventTag 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XFile_ :: CMEventTag 'Json
|
|
|
|
XFileAcpt_ :: CMEventTag 'Json
|
|
|
|
XFileAcptInv_ :: CMEventTag 'Json
|
|
|
|
XFileCancel_ :: CMEventTag 'Json
|
|
|
|
XInfo_ :: CMEventTag 'Json
|
|
|
|
XContact_ :: CMEventTag 'Json
|
2023-09-27 19:36:13 +04:00
|
|
|
XDirectDel_ :: CMEventTag 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XGrpInv_ :: CMEventTag 'Json
|
|
|
|
XGrpAcpt_ :: CMEventTag 'Json
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkInv_ :: CMEventTag 'Json
|
2025-02-25 14:05:49 +04:00
|
|
|
XGrpLinkReject_ :: CMEventTag 'Json
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkMem_ :: CMEventTag 'Json
|
2025-03-03 18:57:29 +00:00
|
|
|
XGrpLinkAcpt_ :: CMEventTag 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XGrpMemNew_ :: CMEventTag 'Json
|
|
|
|
XGrpMemIntro_ :: CMEventTag 'Json
|
|
|
|
XGrpMemInv_ :: CMEventTag 'Json
|
|
|
|
XGrpMemFwd_ :: CMEventTag 'Json
|
|
|
|
XGrpMemInfo_ :: CMEventTag 'Json
|
|
|
|
XGrpMemRole_ :: CMEventTag 'Json
|
2024-01-19 17:57:04 +04:00
|
|
|
XGrpMemRestrict_ :: CMEventTag 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XGrpMemCon_ :: CMEventTag 'Json
|
|
|
|
XGrpMemConAll_ :: CMEventTag 'Json
|
|
|
|
XGrpMemDel_ :: CMEventTag 'Json
|
|
|
|
XGrpLeave_ :: CMEventTag 'Json
|
|
|
|
XGrpDel_ :: CMEventTag 'Json
|
|
|
|
XGrpInfo_ :: CMEventTag 'Json
|
2024-12-05 18:32:00 +00:00
|
|
|
XGrpPrefs_ :: CMEventTag 'Json
|
2023-09-16 17:55:48 +04:00
|
|
|
XGrpDirectInv_ :: CMEventTag 'Json
|
2023-11-18 21:52:01 +04:00
|
|
|
XGrpMsgForward_ :: CMEventTag 'Json
|
2022-10-14 13:06:33 +01:00
|
|
|
XInfoProbe_ :: CMEventTag 'Json
|
|
|
|
XInfoProbeCheck_ :: CMEventTag 'Json
|
|
|
|
XInfoProbeOk_ :: CMEventTag 'Json
|
|
|
|
XCallInv_ :: CMEventTag 'Json
|
|
|
|
XCallOffer_ :: CMEventTag 'Json
|
|
|
|
XCallAnswer_ :: CMEventTag 'Json
|
|
|
|
XCallExtra_ :: CMEventTag 'Json
|
|
|
|
XCallEnd_ :: CMEventTag 'Json
|
|
|
|
XOk_ :: CMEventTag 'Json
|
|
|
|
XUnknown_ :: Text -> CMEventTag 'Json
|
|
|
|
BFileChunk_ :: CMEventTag 'Binary
|
|
|
|
|
|
|
|
deriving instance Show (CMEventTag e)
|
|
|
|
|
|
|
|
deriving instance Eq (CMEventTag e)
|
|
|
|
|
|
|
|
instance MsgEncodingI e => StrEncoding (CMEventTag e) where
|
2022-01-11 08:50:44 +00:00
|
|
|
strEncode = \case
|
|
|
|
XMsgNew_ -> "x.msg.new"
|
2023-03-09 11:01:22 +00:00
|
|
|
XMsgFileDescr_ -> "x.msg.file.descr"
|
2022-03-23 11:37:51 +00:00
|
|
|
XMsgUpdate_ -> "x.msg.update"
|
|
|
|
XMsgDel_ -> "x.msg.del"
|
2022-03-28 20:35:57 +04:00
|
|
|
XMsgDeleted_ -> "x.msg.deleted"
|
2023-05-15 12:28:53 +02:00
|
|
|
XMsgReact_ -> "x.msg.react"
|
2022-01-11 08:50:44 +00:00
|
|
|
XFile_ -> "x.file"
|
|
|
|
XFileAcpt_ -> "x.file.acpt"
|
2022-04-05 10:01:08 +04:00
|
|
|
XFileAcptInv_ -> "x.file.acpt.inv"
|
2022-05-11 16:18:28 +04:00
|
|
|
XFileCancel_ -> "x.file.cancel"
|
2022-01-11 08:50:44 +00:00
|
|
|
XInfo_ -> "x.info"
|
|
|
|
XContact_ -> "x.contact"
|
2023-09-27 19:36:13 +04:00
|
|
|
XDirectDel_ -> "x.direct.del"
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpInv_ -> "x.grp.inv"
|
|
|
|
XGrpAcpt_ -> "x.grp.acpt"
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkInv_ -> "x.grp.link.inv"
|
2025-02-25 14:05:49 +04:00
|
|
|
XGrpLinkReject_ -> "x.grp.link.reject"
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkMem_ -> "x.grp.link.mem"
|
2025-03-03 18:57:29 +00:00
|
|
|
XGrpLinkAcpt_ -> "x.grp.link.acpt"
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpMemNew_ -> "x.grp.mem.new"
|
|
|
|
XGrpMemIntro_ -> "x.grp.mem.intro"
|
|
|
|
XGrpMemInv_ -> "x.grp.mem.inv"
|
|
|
|
XGrpMemFwd_ -> "x.grp.mem.fwd"
|
|
|
|
XGrpMemInfo_ -> "x.grp.mem.info"
|
2022-10-03 09:00:47 +01:00
|
|
|
XGrpMemRole_ -> "x.grp.mem.role"
|
2024-01-19 17:57:04 +04:00
|
|
|
XGrpMemRestrict_ -> "x.grp.mem.restrict"
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpMemCon_ -> "x.grp.mem.con"
|
|
|
|
XGrpMemConAll_ -> "x.grp.mem.con.all"
|
|
|
|
XGrpMemDel_ -> "x.grp.mem.del"
|
|
|
|
XGrpLeave_ -> "x.grp.leave"
|
|
|
|
XGrpDel_ -> "x.grp.del"
|
2022-07-29 19:04:32 +01:00
|
|
|
XGrpInfo_ -> "x.grp.info"
|
2024-12-05 18:32:00 +00:00
|
|
|
XGrpPrefs_ -> "x.grp.prefs"
|
2023-09-16 17:55:48 +04:00
|
|
|
XGrpDirectInv_ -> "x.grp.direct.inv"
|
2023-11-18 21:52:01 +04:00
|
|
|
XGrpMsgForward_ -> "x.grp.msg.forward"
|
2022-01-11 08:50:44 +00:00
|
|
|
XInfoProbe_ -> "x.info.probe"
|
|
|
|
XInfoProbeCheck_ -> "x.info.probe.check"
|
|
|
|
XInfoProbeOk_ -> "x.info.probe.ok"
|
2022-05-02 17:06:49 +01:00
|
|
|
XCallInv_ -> "x.call.inv"
|
|
|
|
XCallOffer_ -> "x.call.offer"
|
|
|
|
XCallAnswer_ -> "x.call.answer"
|
|
|
|
XCallExtra_ -> "x.call.extra"
|
|
|
|
XCallEnd_ -> "x.call.end"
|
2022-01-11 08:50:44 +00:00
|
|
|
XOk_ -> "x.ok"
|
2022-03-13 19:34:03 +00:00
|
|
|
XUnknown_ t -> encodeUtf8 t
|
2022-10-14 13:06:33 +01:00
|
|
|
BFileChunk_ -> "F"
|
|
|
|
strDecode = (\(ACMEventTag _ t) -> checkEncoding t) <=< strDecode
|
2022-01-11 08:50:44 +00:00
|
|
|
strP = strDecode <$?> A.takeTill (== ' ')
|
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
instance StrEncoding ACMEventTag where
|
|
|
|
strEncode (ACMEventTag _ t) = strEncode t
|
|
|
|
strP =
|
|
|
|
((,) <$> A.peekChar' <*> A.takeTill (== ' ')) >>= \case
|
|
|
|
('x', t) -> pure . ACMEventTag SJson $ case t of
|
|
|
|
"x.msg.new" -> XMsgNew_
|
2023-03-09 11:01:22 +00:00
|
|
|
"x.msg.file.descr" -> XMsgFileDescr_
|
2022-10-14 13:06:33 +01:00
|
|
|
"x.msg.update" -> XMsgUpdate_
|
|
|
|
"x.msg.del" -> XMsgDel_
|
|
|
|
"x.msg.deleted" -> XMsgDeleted_
|
2023-05-15 12:28:53 +02:00
|
|
|
"x.msg.react" -> XMsgReact_
|
2022-10-14 13:06:33 +01:00
|
|
|
"x.file" -> XFile_
|
|
|
|
"x.file.acpt" -> XFileAcpt_
|
|
|
|
"x.file.acpt.inv" -> XFileAcptInv_
|
|
|
|
"x.file.cancel" -> XFileCancel_
|
|
|
|
"x.info" -> XInfo_
|
|
|
|
"x.contact" -> XContact_
|
2023-09-27 19:36:13 +04:00
|
|
|
"x.direct.del" -> XDirectDel_
|
2022-10-14 13:06:33 +01:00
|
|
|
"x.grp.inv" -> XGrpInv_
|
|
|
|
"x.grp.acpt" -> XGrpAcpt_
|
2023-10-30 20:40:20 +04:00
|
|
|
"x.grp.link.inv" -> XGrpLinkInv_
|
2025-02-25 14:05:49 +04:00
|
|
|
"x.grp.link.reject" -> XGrpLinkReject_
|
2023-10-30 20:40:20 +04:00
|
|
|
"x.grp.link.mem" -> XGrpLinkMem_
|
2025-03-03 18:57:29 +00:00
|
|
|
"x.grp.link.acpt" -> XGrpLinkAcpt_
|
2022-10-14 13:06:33 +01:00
|
|
|
"x.grp.mem.new" -> XGrpMemNew_
|
|
|
|
"x.grp.mem.intro" -> XGrpMemIntro_
|
|
|
|
"x.grp.mem.inv" -> XGrpMemInv_
|
|
|
|
"x.grp.mem.fwd" -> XGrpMemFwd_
|
|
|
|
"x.grp.mem.info" -> XGrpMemInfo_
|
|
|
|
"x.grp.mem.role" -> XGrpMemRole_
|
2024-01-19 17:57:04 +04:00
|
|
|
"x.grp.mem.restrict" -> XGrpMemRestrict_
|
2022-10-14 13:06:33 +01:00
|
|
|
"x.grp.mem.con" -> XGrpMemCon_
|
|
|
|
"x.grp.mem.con.all" -> XGrpMemConAll_
|
|
|
|
"x.grp.mem.del" -> XGrpMemDel_
|
|
|
|
"x.grp.leave" -> XGrpLeave_
|
|
|
|
"x.grp.del" -> XGrpDel_
|
|
|
|
"x.grp.info" -> XGrpInfo_
|
2024-12-05 18:32:00 +00:00
|
|
|
"x.grp.prefs" -> XGrpPrefs_
|
2023-09-16 17:55:48 +04:00
|
|
|
"x.grp.direct.inv" -> XGrpDirectInv_
|
2023-11-18 21:52:01 +04:00
|
|
|
"x.grp.msg.forward" -> XGrpMsgForward_
|
2022-10-14 13:06:33 +01:00
|
|
|
"x.info.probe" -> XInfoProbe_
|
|
|
|
"x.info.probe.check" -> XInfoProbeCheck_
|
|
|
|
"x.info.probe.ok" -> XInfoProbeOk_
|
|
|
|
"x.call.inv" -> XCallInv_
|
|
|
|
"x.call.offer" -> XCallOffer_
|
|
|
|
"x.call.answer" -> XCallAnswer_
|
|
|
|
"x.call.extra" -> XCallExtra_
|
|
|
|
"x.call.end" -> XCallEnd_
|
|
|
|
"x.ok" -> XOk_
|
|
|
|
_ -> XUnknown_ $ safeDecodeUtf8 t
|
|
|
|
(_, "F") -> pure $ ACMEventTag SBinary BFileChunk_
|
|
|
|
_ -> fail "bad ACMEventTag"
|
|
|
|
|
|
|
|
toCMEventTag :: ChatMsgEvent e -> CMEventTag e
|
|
|
|
toCMEventTag msg = case msg of
|
2022-01-11 08:50:44 +00:00
|
|
|
XMsgNew _ -> XMsgNew_
|
2023-03-09 11:01:22 +00:00
|
|
|
XMsgFileDescr _ _ -> XMsgFileDescr_
|
2022-12-14 12:16:11 +00:00
|
|
|
XMsgUpdate {} -> XMsgUpdate_
|
2023-02-08 07:08:53 +00:00
|
|
|
XMsgDel {} -> XMsgDel_
|
2022-03-28 20:35:57 +04:00
|
|
|
XMsgDeleted -> XMsgDeleted_
|
2023-05-15 12:28:53 +02:00
|
|
|
XMsgReact {} -> XMsgReact_
|
2022-01-11 08:50:44 +00:00
|
|
|
XFile _ -> XFile_
|
|
|
|
XFileAcpt _ -> XFileAcpt_
|
2022-04-05 10:01:08 +04:00
|
|
|
XFileAcptInv {} -> XFileAcptInv_
|
2022-05-11 16:18:28 +04:00
|
|
|
XFileCancel _ -> XFileCancel_
|
2022-01-11 08:50:44 +00:00
|
|
|
XInfo _ -> XInfo_
|
2025-05-27 13:00:52 +00:00
|
|
|
XContact {} -> XContact_
|
2023-09-27 19:36:13 +04:00
|
|
|
XDirectDel -> XDirectDel_
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpInv _ -> XGrpInv_
|
2022-08-27 19:56:03 +04:00
|
|
|
XGrpAcpt _ -> XGrpAcpt_
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkInv _ -> XGrpLinkInv_
|
2025-02-25 14:05:49 +04:00
|
|
|
XGrpLinkReject _ -> XGrpLinkReject_
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkMem _ -> XGrpLinkMem_
|
2025-04-02 07:57:18 +00:00
|
|
|
XGrpLinkAcpt {} -> XGrpLinkAcpt_
|
|
|
|
XGrpMemNew {} -> XGrpMemNew_
|
2024-01-19 17:57:04 +04:00
|
|
|
XGrpMemIntro _ _ -> XGrpMemIntro_
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpMemInv _ _ -> XGrpMemInv_
|
|
|
|
XGrpMemFwd _ _ -> XGrpMemFwd_
|
|
|
|
XGrpMemInfo _ _ -> XGrpMemInfo_
|
2022-10-03 09:00:47 +01:00
|
|
|
XGrpMemRole _ _ -> XGrpMemRole_
|
2024-01-19 17:57:04 +04:00
|
|
|
XGrpMemRestrict _ _ -> XGrpMemRestrict_
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpMemCon _ -> XGrpMemCon_
|
|
|
|
XGrpMemConAll _ -> XGrpMemConAll_
|
2025-03-07 07:47:32 +00:00
|
|
|
XGrpMemDel {} -> XGrpMemDel_
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpLeave -> XGrpLeave_
|
|
|
|
XGrpDel -> XGrpDel_
|
2022-07-29 19:04:32 +01:00
|
|
|
XGrpInfo _ -> XGrpInfo_
|
2024-12-05 18:32:00 +00:00
|
|
|
XGrpPrefs _ -> XGrpPrefs_
|
2025-05-19 11:14:43 +01:00
|
|
|
XGrpDirectInv {} -> XGrpDirectInv_
|
2023-11-18 21:52:01 +04:00
|
|
|
XGrpMsgForward {} -> XGrpMsgForward_
|
2022-01-11 08:50:44 +00:00
|
|
|
XInfoProbe _ -> XInfoProbe_
|
|
|
|
XInfoProbeCheck _ -> XInfoProbeCheck_
|
|
|
|
XInfoProbeOk _ -> XInfoProbeOk_
|
2022-05-02 17:06:49 +01:00
|
|
|
XCallInv _ _ -> XCallInv_
|
|
|
|
XCallOffer _ _ -> XCallOffer_
|
|
|
|
XCallAnswer _ _ -> XCallAnswer_
|
|
|
|
XCallExtra _ _ -> XCallExtra_
|
|
|
|
XCallEnd _ -> XCallEnd_
|
2022-01-11 08:50:44 +00:00
|
|
|
XOk -> XOk_
|
2022-03-13 19:34:03 +00:00
|
|
|
XUnknown t _ -> XUnknown_ t
|
2022-10-14 13:06:33 +01:00
|
|
|
BFileChunk _ _ -> BFileChunk_
|
|
|
|
|
|
|
|
instance MsgEncodingI e => TextEncoding (CMEventTag e) where
|
|
|
|
textEncode = decodeLatin1 . strEncode
|
|
|
|
textDecode = eitherToMaybe . strDecode . encodeUtf8
|
2022-01-11 08:50:44 +00:00
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
instance TextEncoding ACMEventTag where
|
|
|
|
textEncode (ACMEventTag _ t) = textEncode t
|
|
|
|
textDecode = eitherToMaybe . strDecode . encodeUtf8
|
2022-01-24 16:07:17 +00:00
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
instance (MsgEncodingI e, Typeable e) => FromField (CMEventTag e) where fromField = fromTextField_ textDecode
|
2022-01-24 16:07:17 +00:00
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
instance MsgEncodingI e => ToField (CMEventTag e) where toField = toField . textEncode
|
2022-01-24 16:07:17 +00:00
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
instance FromField ACMEventTag where fromField = fromTextField_ textDecode
|
2022-01-11 08:50:44 +00:00
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
instance ToField ACMEventTag where toField = toField . textEncode
|
|
|
|
|
|
|
|
hasNotification :: CMEventTag e -> Bool
|
2022-06-07 14:14:54 +01:00
|
|
|
hasNotification = \case
|
|
|
|
XMsgNew_ -> True
|
|
|
|
XFile_ -> True
|
|
|
|
XContact_ -> True
|
|
|
|
XGrpInv_ -> True
|
2022-08-09 21:46:49 +04:00
|
|
|
XGrpMemFwd_ -> True
|
2022-06-07 14:14:54 +01:00
|
|
|
XGrpDel_ -> True
|
|
|
|
XCallInv_ -> True
|
|
|
|
_ -> False
|
|
|
|
|
2023-07-13 23:48:25 +01:00
|
|
|
hasDeliveryReceipt :: CMEventTag e -> Bool
|
|
|
|
hasDeliveryReceipt = \case
|
|
|
|
XMsgNew_ -> True
|
|
|
|
XGrpInv_ -> True
|
|
|
|
XCallInv_ -> True
|
|
|
|
_ -> False
|
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
appBinaryToCM :: AppMessageBinary -> Either String (ChatMessage 'Binary)
|
|
|
|
appBinaryToCM AppMessageBinary {msgId, tag, body} = do
|
|
|
|
eventTag <- strDecode $ B.singleton tag
|
|
|
|
chatMsgEvent <- parseAll (msg eventTag) body
|
2023-09-01 19:20:07 +04:00
|
|
|
pure ChatMessage {chatVRange = chatInitialVRange, msgId, chatMsgEvent}
|
2022-10-14 13:06:33 +01:00
|
|
|
where
|
|
|
|
msg :: CMEventTag 'Binary -> A.Parser (ChatMsgEvent 'Binary)
|
|
|
|
msg = \case
|
|
|
|
BFileChunk_ -> BFileChunk <$> (SharedMsgId <$> smpP) <*> (unIFC <$> smpP)
|
|
|
|
|
|
|
|
appJsonToCM :: AppMessageJson -> Either String (ChatMessage 'Json)
|
2023-09-01 19:20:07 +04:00
|
|
|
appJsonToCM AppMessageJson {v, msgId, event, params} = do
|
2022-01-11 08:50:44 +00:00
|
|
|
eventTag <- strDecode $ encodeUtf8 event
|
|
|
|
chatMsgEvent <- msg eventTag
|
2023-09-01 19:20:07 +04:00
|
|
|
pure ChatMessage {chatVRange = maybe chatInitialVRange fromChatVRange v, msgId, chatMsgEvent}
|
2021-07-11 12:22:22 +01:00
|
|
|
where
|
2022-01-29 20:21:37 +00:00
|
|
|
p :: FromJSON a => J.Key -> Either String a
|
2022-01-11 08:50:44 +00:00
|
|
|
p key = JT.parseEither (.: key) params
|
2022-03-13 19:34:03 +00:00
|
|
|
opt :: FromJSON a => J.Key -> Either String (Maybe a)
|
|
|
|
opt key = JT.parseEither (.:? key) params
|
2022-10-14 13:06:33 +01:00
|
|
|
msg :: CMEventTag 'Json -> Either String (ChatMsgEvent 'Json)
|
2022-01-11 08:50:44 +00:00
|
|
|
msg = \case
|
2022-03-28 20:35:57 +04:00
|
|
|
XMsgNew_ -> XMsgNew <$> JT.parseEither parseMsgContainer params
|
2023-03-09 11:01:22 +00:00
|
|
|
XMsgFileDescr_ -> XMsgFileDescr <$> p "msgId" <*> p "fileDescr"
|
2025-05-19 11:14:43 +01:00
|
|
|
XMsgUpdate_ -> do
|
|
|
|
msgId' <- p "msgId"
|
|
|
|
content <- p "content"
|
|
|
|
mentions <- fromMaybe M.empty <$> opt "mentions"
|
|
|
|
ttl <- opt "ttl"
|
|
|
|
live <- opt "live"
|
|
|
|
scope <- opt "scope"
|
|
|
|
pure XMsgUpdate {msgId = msgId', content, mentions, ttl, live, scope}
|
2023-02-08 07:08:53 +00:00
|
|
|
XMsgDel_ -> XMsgDel <$> p "msgId" <*> opt "memberId"
|
2022-03-28 20:35:57 +04:00
|
|
|
XMsgDeleted_ -> pure XMsgDeleted
|
2023-05-15 12:28:53 +02:00
|
|
|
XMsgReact_ -> XMsgReact <$> p "msgId" <*> opt "memberId" <*> p "reaction" <*> p "add"
|
2022-01-11 08:50:44 +00:00
|
|
|
XFile_ -> XFile <$> p "file"
|
|
|
|
XFileAcpt_ -> XFileAcpt <$> p "fileName"
|
2022-10-14 13:06:33 +01:00
|
|
|
XFileAcptInv_ -> XFileAcptInv <$> p "msgId" <*> opt "fileConnReq" <*> p "fileName"
|
2022-05-11 16:18:28 +04:00
|
|
|
XFileCancel_ -> XFileCancel <$> p "msgId"
|
2022-01-11 08:50:44 +00:00
|
|
|
XInfo_ -> XInfo <$> p "profile"
|
2025-06-23 14:42:00 +01:00
|
|
|
XContact_ -> do
|
|
|
|
profile <- p "profile"
|
|
|
|
contactReqId <- opt "contactReqId"
|
|
|
|
welcomeMsgId <- opt "welcomeMsgId"
|
|
|
|
reqMsgId <- opt "msgId"
|
|
|
|
reqContent <- opt "content"
|
|
|
|
let requestMsg = (,) <$> reqMsgId <*> reqContent
|
|
|
|
pure XContact {profile, contactReqId, welcomeMsgId, requestMsg}
|
2023-09-27 19:36:13 +04:00
|
|
|
XDirectDel_ -> pure XDirectDel
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpInv_ -> XGrpInv <$> p "groupInvitation"
|
2022-08-27 19:56:03 +04:00
|
|
|
XGrpAcpt_ -> XGrpAcpt <$> p "memberId"
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkInv_ -> XGrpLinkInv <$> p "groupLinkInvitation"
|
2025-02-25 14:05:49 +04:00
|
|
|
XGrpLinkReject_ -> XGrpLinkReject <$> p "groupLinkRejection"
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkMem_ -> XGrpLinkMem <$> p "profile"
|
2025-04-28 06:28:40 +00:00
|
|
|
XGrpLinkAcpt_ -> XGrpLinkAcpt <$> p "acceptance" <*> p "role" <*> p "memberId"
|
2025-04-02 07:57:18 +00:00
|
|
|
XGrpMemNew_ -> XGrpMemNew <$> p "memberInfo" <*> opt "scope"
|
2024-01-19 17:57:04 +04:00
|
|
|
XGrpMemIntro_ -> XGrpMemIntro <$> p "memberInfo" <*> opt "memberRestrictions"
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpMemInv_ -> XGrpMemInv <$> p "memberId" <*> p "memberIntro"
|
|
|
|
XGrpMemFwd_ -> XGrpMemFwd <$> p "memberInfo" <*> p "memberIntro"
|
|
|
|
XGrpMemInfo_ -> XGrpMemInfo <$> p "memberId" <*> p "profile"
|
2022-10-03 09:00:47 +01:00
|
|
|
XGrpMemRole_ -> XGrpMemRole <$> p "memberId" <*> p "role"
|
2024-01-19 17:57:04 +04:00
|
|
|
XGrpMemRestrict_ -> XGrpMemRestrict <$> p "memberId" <*> p "memberRestrictions"
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpMemCon_ -> XGrpMemCon <$> p "memberId"
|
|
|
|
XGrpMemConAll_ -> XGrpMemConAll <$> p "memberId"
|
2025-03-07 07:47:32 +00:00
|
|
|
XGrpMemDel_ -> XGrpMemDel <$> p "memberId" <*> Right (fromRight False $ p "messages")
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpLeave_ -> pure XGrpLeave
|
|
|
|
XGrpDel_ -> pure XGrpDel
|
2022-07-29 19:04:32 +01:00
|
|
|
XGrpInfo_ -> XGrpInfo <$> p "groupProfile"
|
2024-12-05 18:32:00 +00:00
|
|
|
XGrpPrefs_ -> XGrpPrefs <$> p "groupPreferences"
|
2025-05-19 11:14:43 +01:00
|
|
|
XGrpDirectInv_ -> XGrpDirectInv <$> p "connReq" <*> opt "content" <*> opt "scope"
|
2023-11-18 21:52:01 +04:00
|
|
|
XGrpMsgForward_ -> XGrpMsgForward <$> p "memberId" <*> p "msg" <*> p "msgTs"
|
2022-01-11 08:50:44 +00:00
|
|
|
XInfoProbe_ -> XInfoProbe <$> p "probe"
|
|
|
|
XInfoProbeCheck_ -> XInfoProbeCheck <$> p "probeHash"
|
|
|
|
XInfoProbeOk_ -> XInfoProbeOk <$> p "probe"
|
2022-05-02 17:06:49 +01:00
|
|
|
XCallInv_ -> XCallInv <$> p "callId" <*> p "invitation"
|
|
|
|
XCallOffer_ -> XCallOffer <$> p "callId" <*> p "offer"
|
|
|
|
XCallAnswer_ -> XCallAnswer <$> p "callId" <*> p "answer"
|
|
|
|
XCallExtra_ -> XCallExtra <$> p "callId" <*> p "extra"
|
|
|
|
XCallEnd_ -> XCallEnd <$> p "callId"
|
2022-01-11 08:50:44 +00:00
|
|
|
XOk_ -> pure XOk
|
2022-03-13 19:34:03 +00:00
|
|
|
XUnknown_ t -> pure $ XUnknown t params
|
2022-01-11 08:50:44 +00:00
|
|
|
|
2022-12-14 12:16:11 +00:00
|
|
|
(.=?) :: ToJSON v => JT.Key -> Maybe v -> [(J.Key, J.Value)] -> [(J.Key, J.Value)]
|
|
|
|
key .=? value = maybe id ((:) . (key .=)) value
|
|
|
|
|
2022-10-14 13:06:33 +01:00
|
|
|
chatToAppMessage :: forall e. MsgEncodingI e => ChatMessage e -> AppMessage e
|
2023-09-01 19:20:07 +04:00
|
|
|
chatToAppMessage ChatMessage {chatVRange, msgId, chatMsgEvent} = case encoding @e of
|
2022-10-14 13:06:33 +01:00
|
|
|
SBinary ->
|
|
|
|
let (binaryMsgId, body) = toBody chatMsgEvent
|
|
|
|
in AMBinary AppMessageBinary {msgId = binaryMsgId, tag = B.head $ strEncode tag, body}
|
2023-09-01 19:20:07 +04:00
|
|
|
SJson -> AMJson AppMessageJson {v = Just $ ChatVersionRange chatVRange, msgId, event = textEncode tag, params = params chatMsgEvent}
|
2021-07-06 19:07:03 +01:00
|
|
|
where
|
2022-10-14 13:06:33 +01:00
|
|
|
tag = toCMEventTag chatMsgEvent
|
2022-01-29 20:21:37 +00:00
|
|
|
o :: [(J.Key, J.Value)] -> J.Object
|
|
|
|
o = JM.fromList
|
2022-10-14 13:06:33 +01:00
|
|
|
toBody :: ChatMsgEvent 'Binary -> (Maybe SharedMsgId, ByteString)
|
|
|
|
toBody = \case
|
|
|
|
BFileChunk (SharedMsgId msgId') chunk -> (Nothing, smpEncode (msgId', IFC chunk))
|
|
|
|
params :: ChatMsgEvent 'Json -> J.Object
|
|
|
|
params = \case
|
2022-03-13 19:34:03 +00:00
|
|
|
XMsgNew container -> msgContainerJSON container
|
2023-03-09 11:01:22 +00:00
|
|
|
XMsgFileDescr msgId' fileDescr -> o ["msgId" .= msgId', "fileDescr" .= fileDescr]
|
2025-05-19 11:14:43 +01:00
|
|
|
XMsgUpdate {msgId = msgId', content, mentions, ttl, live, scope} -> o $ ("ttl" .=? ttl) $ ("live" .=? live) $ ("scope" .=? scope) $ ("mentions" .=? nonEmptyMap mentions) ["msgId" .= msgId', "content" .= content]
|
2023-02-08 07:08:53 +00:00
|
|
|
XMsgDel msgId' memberId -> o $ ("memberId" .=? memberId) ["msgId" .= msgId']
|
2022-03-28 20:35:57 +04:00
|
|
|
XMsgDeleted -> JM.empty
|
2023-09-01 19:20:07 +04:00
|
|
|
XMsgReact msgId' memberId reaction add -> o $ ("memberId" .=? memberId) ["msgId" .= msgId', "reaction" .= reaction, "add" .= add]
|
2022-10-14 13:06:33 +01:00
|
|
|
XFile fileInv -> o ["file" .= fileInv]
|
2022-01-11 08:50:44 +00:00
|
|
|
XFileAcpt fileName -> o ["fileName" .= fileName]
|
2022-10-14 13:06:33 +01:00
|
|
|
XFileAcptInv sharedMsgId fileConnReq fileName -> o $ ("fileConnReq" .=? fileConnReq) ["msgId" .= sharedMsgId, "fileName" .= fileName]
|
2022-05-11 16:18:28 +04:00
|
|
|
XFileCancel sharedMsgId -> o ["msgId" .= sharedMsgId]
|
2022-03-19 09:04:53 +00:00
|
|
|
XInfo profile -> o ["profile" .= profile]
|
2025-06-23 14:42:00 +01:00
|
|
|
XContact {profile, contactReqId, welcomeMsgId, requestMsg} -> o $ ("contactReqId" .=? contactReqId) $ ("welcomeMsgId" .=? welcomeMsgId) $ ("msgId" .=? (fst <$> requestMsg)) $ ("content" .=? (snd <$> requestMsg)) $ ["profile" .= profile]
|
2023-09-27 19:36:13 +04:00
|
|
|
XDirectDel -> JM.empty
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpInv groupInv -> o ["groupInvitation" .= groupInv]
|
2022-08-27 19:56:03 +04:00
|
|
|
XGrpAcpt memId -> o ["memberId" .= memId]
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkInv groupLinkInv -> o ["groupLinkInvitation" .= groupLinkInv]
|
2025-02-25 14:05:49 +04:00
|
|
|
XGrpLinkReject groupLinkRjct -> o ["groupLinkRejection" .= groupLinkRjct]
|
2023-10-30 20:40:20 +04:00
|
|
|
XGrpLinkMem profile -> o ["profile" .= profile]
|
2025-04-28 06:28:40 +00:00
|
|
|
XGrpLinkAcpt acceptance role memberId -> o ["acceptance" .= acceptance, "role" .= role, "memberId" .= memberId]
|
2025-04-02 07:57:18 +00:00
|
|
|
XGrpMemNew memInfo scope -> o $ ("scope" .=? scope) ["memberInfo" .= memInfo]
|
2024-01-19 17:57:04 +04:00
|
|
|
XGrpMemIntro memInfo memRestrictions -> o $ ("memberRestrictions" .=? memRestrictions) ["memberInfo" .= memInfo]
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpMemInv memId memIntro -> o ["memberId" .= memId, "memberIntro" .= memIntro]
|
|
|
|
XGrpMemFwd memInfo memIntro -> o ["memberInfo" .= memInfo, "memberIntro" .= memIntro]
|
|
|
|
XGrpMemInfo memId profile -> o ["memberId" .= memId, "profile" .= profile]
|
2022-10-03 09:00:47 +01:00
|
|
|
XGrpMemRole memId role -> o ["memberId" .= memId, "role" .= role]
|
2024-01-19 17:57:04 +04:00
|
|
|
XGrpMemRestrict memId memRestrictions -> o ["memberId" .= memId, "memberRestrictions" .= memRestrictions]
|
2022-01-11 08:50:44 +00:00
|
|
|
XGrpMemCon memId -> o ["memberId" .= memId]
|
|
|
|
XGrpMemConAll memId -> o ["memberId" .= memId]
|
2025-03-07 07:47:32 +00:00
|
|
|
XGrpMemDel memId messages -> o $ ("messages" .=? if messages then Just True else Nothing) ["memberId" .= memId]
|
2022-01-29 20:21:37 +00:00
|
|
|
XGrpLeave -> JM.empty
|
|
|
|
XGrpDel -> JM.empty
|
2022-07-29 19:04:32 +01:00
|
|
|
XGrpInfo p -> o ["groupProfile" .= p]
|
2024-12-05 18:32:00 +00:00
|
|
|
XGrpPrefs p -> o ["groupPreferences" .= p]
|
2025-05-19 11:14:43 +01:00
|
|
|
XGrpDirectInv connReq content scope -> o $ ("content" .=? content) $ ("scope" .=? scope) ["connReq" .= connReq]
|
2023-11-18 21:52:01 +04:00
|
|
|
XGrpMsgForward memberId msg msgTs -> o ["memberId" .= memberId, "msg" .= msg, "msgTs" .= msgTs]
|
2022-01-11 08:50:44 +00:00
|
|
|
XInfoProbe probe -> o ["probe" .= probe]
|
|
|
|
XInfoProbeCheck probeHash -> o ["probeHash" .= probeHash]
|
|
|
|
XInfoProbeOk probe -> o ["probe" .= probe]
|
2022-05-02 17:06:49 +01:00
|
|
|
XCallInv callId inv -> o ["callId" .= callId, "invitation" .= inv]
|
|
|
|
XCallOffer callId offer -> o ["callId" .= callId, "offer" .= offer]
|
|
|
|
XCallAnswer callId answer -> o ["callId" .= callId, "answer" .= answer]
|
|
|
|
XCallExtra callId extra -> o ["callId" .= callId, "extra" .= extra]
|
|
|
|
XCallEnd callId -> o ["callId" .= callId]
|
2022-01-29 20:21:37 +00:00
|
|
|
XOk -> JM.empty
|
2022-03-13 19:34:03 +00:00
|
|
|
XUnknown _ ps -> ps
|
2023-11-18 21:52:01 +04:00
|
|
|
|
|
|
|
instance ToJSON (ChatMessage 'Json) where
|
|
|
|
toJSON = (\(AMJson msg) -> toJSON msg) . chatToAppMessage
|
|
|
|
|
|
|
|
instance FromJSON (ChatMessage 'Json) where
|
|
|
|
parseJSON v = appJsonToCM <$?> parseJSON v
|
2025-06-27 20:52:42 +01:00
|
|
|
|
|
|
|
data ContactShortLinkData = ContactShortLinkData
|
|
|
|
{ profile :: Profile,
|
|
|
|
message :: Maybe MsgContent,
|
|
|
|
business :: Bool
|
|
|
|
}
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
data GroupShortLinkData = GroupShortLinkData
|
|
|
|
{ groupProfile :: GroupProfile
|
|
|
|
}
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
$(JQ.deriveJSON defaultJSON ''ContactShortLinkData)
|
|
|
|
|
|
|
|
$(JQ.deriveJSON defaultJSON ''GroupShortLinkData)
|