88{-# LANGUAGE ScopedTypeVariables #-}
99{-# LANGUAGE TypeApplications #-}
1010{-# LANGUAGE TupleSections #-}
11+ {-# LANGUAGE DataKinds #-}
12+ {-# LANGUAGE PolyKinds #-}
13+ {-# LANGUAGE GADTs #-}
14+ {-# LANGUAGE FlexibleInstances #-}
15+ {-# LANGUAGE FlexibleContexts #-}
16+ {-# LANGUAGE UndecidableInstances #-}
1117
1218-- | This module contains the client-server API
1319-- https://matrix.org/docs/spec/client_server/r0.6.1
@@ -39,6 +45,7 @@ module Network.Matrix.Client
3945
4046 -- * Room Events
4147 EventType (.. ),
48+ EventTypeTag (.. ),
4249 MRCreate (.. ),
4350 MRCanonicalAlias (.. ),
4451 MRGuestAccess (.. ),
@@ -49,6 +56,7 @@ module Network.Matrix.Client
4956 StateKey (.. ),
5057 StateEvent (.. ),
5158 StateContent (.. ),
59+ Some (.. ),
5260 getRoomEvent ,
5361 getRoomMembers ,
5462 getRoomState ,
@@ -157,10 +165,9 @@ import qualified Network.URI as URI
157165import Data.Coerce
158166import Data.Bifunctor (bimap )
159167import Data.List (intersperse )
160- import Data.Aeson.Types (Parser )
161- import Control.Applicative
162168import qualified Data.ByteString as B
163169import qualified Data.ByteString.Lazy as BL
170+ import Data.Foldable
164171
165172-- $setup
166173-- >>> import Data.Aeson (decode)
@@ -273,113 +280,177 @@ newtype StateKey = StateKey T.Text
273280 deriving stock Show
274281 deriving newtype FromJSON
275282
276- newtype EventType = EventType T. Text
283+ data EventType
284+ = RoomCreate
285+ | RoomCanonicalAlias
286+ | RoomGuestAccess
287+ | RoomHistoryVisibility
288+ | RoomName
289+ | RoomTopic
290+ | Other
277291 deriving stock Show
278- deriving newtype FromJSON
292+
293+ data EventTypeTag et where
294+ RoomCreateType :: EventTypeTag 'RoomCreate
295+ RoomCanonicalAliasType :: EventTypeTag 'RoomCanonicalAlias
296+ RoomGuestAccessType :: EventTypeTag 'RoomGuestAccess
297+ RoomHistoryVisibilityType :: EventTypeTag 'RoomHistoryVisibility
298+ RoomNameType :: EventTypeTag 'RoomName
299+ RoomTopicType :: EventTypeTag 'RoomTopic
300+ OtherType :: T. Text -> EventTypeTag 'Other
301+
302+ class Render a where
303+ render :: a -> T. Text
304+
305+ instance Show (EventTypeTag 'RoomCreate) where
306+ show _ = " RoomCreateType"
307+
308+ instance Show (EventTypeTag 'RoomCanonicalAlias) where
309+ show _ = " RoomCanonicalAliasType"
310+
311+ instance Show (EventTypeTag 'RoomGuestAccess) where
312+ show _ = " RoomGuestAccessType"
313+
314+ instance Show (EventTypeTag 'RoomHistoryVisibility) where
315+ show _ = " RoomHistoryVisibilityType"
316+
317+ instance Show (EventTypeTag 'RoomName) where
318+ show _ = " RoomNameType"
319+
320+ instance Show (EventTypeTag 'RoomTopic) where
321+ show _ = " RoomTopicType"
322+
323+ instance Show (EventTypeTag 'Other) where
324+ show (OtherType txt) = " OtherType " <> show txt
325+
326+ instance Render (EventTypeTag 'RoomCreate) where
327+ render RoomCreateType = " m.room.create"
328+
329+ instance Render (EventTypeTag 'RoomCanonicalAlias) where
330+ render RoomCanonicalAliasType = " m.room.canonical_alias"
331+
332+ instance Render (EventTypeTag 'RoomGuestAccess) where
333+ render RoomGuestAccessType = " m.room.guest_access"
334+
335+ instance Render (EventTypeTag 'RoomHistoryVisibility) where
336+ render RoomHistoryVisibilityType = " m.room.history_visibility"
337+
338+ instance Render (EventTypeTag 'RoomName) where
339+ render RoomNameType = " m.room.name"
340+
341+ instance Render (EventTypeTag 'RoomTopic) where
342+ render RoomTopicType = " m.room.topic"
343+
344+ instance Render (EventTypeTag 'Other) where
345+ render (OtherType txt) = txt
346+
347+ instance FromJSON (EventTypeTag 'RoomCreate) where
348+ parseJSON = withText " et" $ \ case
349+ " m.room.create" -> pure $ RoomCreateType
350+ _ -> fail " Unexpected event type"
351+
352+ instance FromJSON (EventTypeTag 'RoomCanonicalAlias) where
353+ parseJSON = withText " et" $ \ case
354+ " m.room.canonical_alias" -> pure $ RoomCanonicalAliasType
355+ _ -> fail " Unexpected event type"
356+
357+ instance FromJSON (EventTypeTag 'RoomGuestAccess) where
358+ parseJSON = withText " et" $ \ case
359+ " m.room.guest_access" -> pure $ RoomGuestAccessType
360+ _ -> fail " Unexpected event type"
361+
362+ instance FromJSON (EventTypeTag 'RoomHistoryVisibility) where
363+ parseJSON = withText " et" $ \ case
364+ " m.room.history_visibility" -> pure $ RoomHistoryVisibilityType
365+ _ -> fail " Unexpected event type"
366+
367+ instance FromJSON (EventTypeTag 'RoomName) where
368+ parseJSON = withText " et" $ \ case
369+ " m.room.name" -> pure $ RoomNameType
370+ _ -> fail " Unexpected event type"
371+
372+ instance FromJSON (EventTypeTag 'RoomTopic) where
373+ parseJSON = withText " et" $ \ case
374+ " m.room.topic" -> pure $ RoomTopicType
375+ _ -> fail " Unexpected event type"
376+
377+ instance FromJSON (EventTypeTag 'Other) where
378+ parseJSON = withText " et" (pure . OtherType )
279379
280380data MRCreate = MRCreate { mrcCreator :: UserID , mrcRoomVersion :: Integer }
281381 deriving Show
282382
283- instance FromJSON MRCreate where
284- parseJSON = withObject " RoomCreate" $ \ o -> do
285- mrcCreator <- o .: " creator"
286- mrcRoomVersion <- o .: " room_version"
287- pure $ MRCreate {.. }
288-
289383newtype MRName = MRName { mrnName :: T. Text }
290384 deriving Show
291385
292- instance FromJSON MRName where
293- parseJSON = withObject " RoomName" $ \ o ->
294- MRName <$> (o .: " name" )
295-
296386newtype MRCanonicalAlias = MRCanonicalAlias { mrcAlias :: T. Text }
297387 deriving Show
298388
299- instance FromJSON MRCanonicalAlias where
300- parseJSON = withObject " RoomCanonicalAlias" $ \ o ->
301- MRCanonicalAlias <$> (o .: " alias" )
302389
303390newtype MRGuestAccess = MRGuestAccess { mrGuestAccess :: T. Text }
304391 deriving Show
305392
306- instance FromJSON MRGuestAccess where
307- parseJSON = withObject " GuestAccess" $ \ o ->
308- MRGuestAccess <$> (o .: " guest_access" )
309-
310393newtype MRHistoryVisibility = MRHistoryVisibility { mrHistoryVisibility :: T. Text }
311394 deriving Show
312395
313- instance FromJSON MRHistoryVisibility where
314- parseJSON = withObject " HistoryVisibility" $ \ o ->
315- MRHistoryVisibility <$> (o .: " history_visibility" )
316-
317396newtype MRTopic = MRTopic { mrTopic :: T. Text }
318397 deriving Show
319398
320- instance FromJSON MRTopic where
321- parseJSON = withObject " RoomTopic" $ \ o ->
322- MRTopic <$> (o .: " topic" )
323-
324- data StateContent =
325- StRoomCreate MRCreate
326- -- | StRoomMember MRMember
327- -- | StRoomPowerLevels MRPowerLevels
328- -- | StRoomJoinRules MRJoinRules
329- | StRoomCanonicalAlias MRCanonicalAlias
330- | StRoomGuestAccess MRGuestAccess
331- | StRoomHistoryVisibility MRHistoryVisibility
332- | StRoomName MRName
333- | StRoomTopic MRTopic
334- | StOther Value
335- --- | StSpaceParent MRSpaceParent
336- deriving Show
337-
338- pStRoomCreate :: Value -> Parser StateContent
339- pStRoomCreate v = StRoomCreate <$> parseJSON v
399+ data StateContent et where
400+ ScRoomCreate :: MRCreate -> StateContent 'RoomCreate
401+ -- ScRoomMember :: MRMember -> StateContent 'RoomMember
402+ -- ScRoomPowerLevels :: MRPowerLevels -> StateContent 'RoomPowerLevels
403+ -- ScRoomJoinRules :: MRJoinRules -> StateContent 'RoomJoinRules
404+ ScRoomCanonicalAlias :: MRCanonicalAlias -> StateContent 'RoomCanonicalAlias
405+ ScRoomGuestAccess :: MRGuestAccess -> StateContent 'RoomGuestAccess
406+ ScRoomHistoryVisibility :: MRHistoryVisibility -> StateContent 'RoomHistoryVisibility
407+ ScRoomName :: MRName -> StateContent 'RoomName
408+ ScRoomTopic :: MRTopic -> StateContent 'RoomTopic
409+ ScOther :: Value -> StateContent 'Other
410+
411+ instance FromJSON (StateContent 'RoomCreate) where
412+ parseJSON = withObject " RoomCreate" $ \ o -> do
413+ mrcCreator <- o .: " creator"
414+ mrcRoomVersion <- o .: " room_version"
415+ pure $ ScRoomCreate $ MRCreate {.. }
340416
341- pStRoomCanonicAlias :: Value -> Parser StateContent
342- pStRoomCanonicAlias v = StRoomCanonicalAlias <$> parseJSON v
417+ instance FromJSON (StateContent 'RoomCanonicalAlias) where
418+ parseJSON = withObject " RoomCanonicalAlias" $ \ o ->
419+ ScRoomCanonicalAlias . MRCanonicalAlias <$> (o .: " alias" )
343420
344- pStRoomGuestAccess :: Value -> Parser StateContent
345- pStRoomGuestAccess v = StRoomGuestAccess <$> parseJSON v
421+ instance FromJSON (StateContent 'RoomGuestAccess) where
422+ parseJSON = withObject " GuestAccess" $ \ o ->
423+ ScRoomGuestAccess . MRGuestAccess <$> (o .: " guest_access" )
346424
347- pStRoomHistoryVisibility :: Value -> Parser StateContent
348- pStRoomHistoryVisibility v = StRoomHistoryVisibility <$> parseJSON v
425+ instance FromJSON (StateContent 'RoomHistoryVisibility) where
426+ parseJSON = withObject " HistoryVisibility" $ \ o ->
427+ ScRoomHistoryVisibility . MRHistoryVisibility <$> (o .: " history_visibility" )
349428
350- pStRoomName :: Value -> Parser StateContent
351- pStRoomName v = StRoomName <$> parseJSON v
429+ instance FromJSON (StateContent 'RoomName) where
430+ parseJSON = withObject " RoomName" $ \ o ->
431+ ScRoomName . MRName <$> (o .: " name" )
352432
353- pStRoomTopic :: Value -> Parser StateContent
354- pStRoomTopic v = StRoomTopic <$> parseJSON v
433+ instance FromJSON (StateContent 'RoomTopic) where
434+ parseJSON = withObject " RoomTopic" $ \ o ->
435+ ScRoomTopic . MRTopic <$> (o .: " topic" )
355436
356- pStRoomOther :: Value -> Parser StateContent
357- pStRoomOther v = StOther <$> parseJSON v
358-
359- instance FromJSON StateContent where
360- parseJSON v =
361- pStRoomCreate v
362- <|> pStRoomCanonicAlias v
363- <|> pStRoomGuestAccess v
364- <|> pStRoomHistoryVisibility v
365- <|> pStRoomName v
366- <|> pStRoomTopic v
367- <|> pStRoomOther v
437+ instance FromJSON (StateContent 'Other) where
438+ parseJSON = pure . ScOther
368439
369440-- TODO(SOLOMON): Should This constructor be in 'Event'?
370- data StateEvent = StateEvent
371- { seContent :: StateContent
441+ data StateEvent et = StateEvent
442+ { seContent :: StateContent et
372443 , seEventId :: EventID
373444 , seOriginServerTimestamp :: Integer
374445 , sePreviousContent :: Maybe Value
375446 , seRoomId :: RoomID
376447 , seSender :: UserID
377448 , seStateKey :: StateKey
378- , seEventType :: EventType
449+ , seEventType :: EventTypeTag et
379450 , seUnsigned :: Maybe Value
380- } deriving Show
451+ } deriving stock Generic
381452
382- instance FromJSON StateEvent where
453+ instance ( FromJSON ( StateContent et ), FromJSON ( EventTypeTag et )) => FromJSON ( StateEvent et ) where
383454 parseJSON = withObject " StateEvent" $ \ o -> do
384455 seContent <- o .: " content"
385456 seEventId <- fmap EventID $ o .: " event_id"
@@ -394,7 +465,7 @@ instance FromJSON StateEvent where
394465
395466-- | Get the state events for the current state of a room.
396467-- https://spec.matrix.org/v1.1/client-server-api/#get_matrixclientv3roomsroomidstate
397- getRoomState :: ClientSession -> RoomID -> MatrixIO [StateEvent ]
468+ getRoomState :: ClientSession -> RoomID -> MatrixIO [Some StateEvent ]
398469getRoomState session (RoomID rid) = do
399470 request <- mkRequest session True $ " /_matrix/client/v3/rooms/" <> rid <> " /state"
400471 doRequest session request
@@ -404,9 +475,10 @@ getRoomState session (RoomID rid) = do
404475-- of the room. If the user has left the room then the state is taken
405476-- from the state of the room when they left.
406477-- https://spec.matrix.org/v1.1/client-server-api/#get_matrixclientv3roomsroomidstateeventtypestatekey
407- getRoomStateEvent :: ClientSession -> RoomID -> EventType -> StateKey -> MatrixIO StateEvent
408- getRoomStateEvent session (RoomID rid) (EventType et) (StateKey key) = do
409- request <- mkRequest session True $ " /_matrix/client/v3/rooms/" <> rid <> " /state" <> et <> " /" <> key
478+ getRoomStateEvent :: (FromJSON (EventTypeTag et ), FromJSON (StateContent et ), Render (EventTypeTag et )) =>
479+ ClientSession -> RoomID -> (EventTypeTag et ) -> StateKey -> MatrixIO (StateEvent et )
480+ getRoomStateEvent session (RoomID rid) et (StateKey key) = do
481+ request <- mkRequest session True $ " /_matrix/client/v3/rooms/" <> rid <> " /state/" <> render et <> " /" <> key
410482 doRequest session request
411483
412484data Dir = F | B
@@ -415,15 +487,28 @@ renderDir :: Dir -> B.ByteString
415487renderDir F = " f"
416488renderDir B = " b"
417489
490+
491+ instance FromJSON (Some StateEvent ) where
492+ parseJSON val = asum [ Some <$> parseJSON @ (StateEvent 'RoomCreate) val
493+ , Some <$> parseJSON @ (StateEvent 'RoomCanonicalAlias) val
494+ , Some <$> parseJSON @ (StateEvent 'RoomGuestAccess) val
495+ , Some <$> parseJSON @ (StateEvent 'RoomHistoryVisibility) val
496+ , Some <$> parseJSON @ (StateEvent 'RoomName) val
497+ , Some <$> parseJSON @ (StateEvent 'RoomTopic) val
498+ , Some <$> parseJSON @ (StateEvent 'Other) val
499+ ]
500+
501+ data Some f = forall x . Some (f x )
502+
418503data PaginatedRoomMessages = PaginatedRoomMessages
419504 { chunk :: [RoomEvent ]
420505 , end :: Maybe T. Text
421506 -- ^ A token corresponding to the end of chunk.
422507 , start :: T. Text
423508 -- ^ A token corresponding to the start of chunk.
424- , state :: [StateEvent ]
509+ , state :: [Some StateEvent ]
425510 -- ^ A list of state events relevant to showing the chunk.
426- } deriving Show
511+ }
427512
428513instance FromJSON PaginatedRoomMessages where
429514 parseJSON = withObject " PaginatedRoomMessages" $ \ o -> do
@@ -461,9 +546,9 @@ getRoomMessages session (RoomID rid) dir roomFilter fromToken limit toToken = do
461546-- | Send arbitrary state events to a room. These events will be overwritten if
462547-- <room id>, <event type> and <state key> all match.
463548-- https://spec.matrix.org/v1.1/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey
464- sendRoomStateEvent :: ClientSession -> RoomID -> EventType -> StateKey -> Value -> MatrixIO EventID
465- sendRoomStateEvent session (RoomID rid) ( EventType et) (StateKey key) event = do
466- request <- mkRequest session True $ " /_matrix/client/v3/rooms/" <> escapeUriComponent rid <> " /state/" <> escapeUriComponent et <> " /" <> escapeUriComponent key
549+ sendRoomStateEvent :: Render ( EventTypeTag et ) => ClientSession -> RoomID -> EventTypeTag et -> StateKey -> Value -> MatrixIO EventID
550+ sendRoomStateEvent session (RoomID rid) et (StateKey key) event = do
551+ request <- mkRequest session True $ " /_matrix/client/v3/rooms/" <> escapeUriComponent rid <> " /state/" <> escapeUriComponent (render et) <> " /" <> escapeUriComponent key
467552 doRequest session $
468553 request { HTTP. method = " PUT"
469554 , HTTP. requestBody = HTTP. RequestBodyLBS $ encode event
0 commit comments