{-# LANGUAGE OverloadedStrings #-} module Network.Pushbullet.Types.Push ( Push(..) , PushId(..) , PushDirection(..) , PushTarget(..) , PushData(..) , PushOrigin(..) , ExistingPushes(..) , simpleNewPush ) where import Network.Pushbullet.Types.Device import Network.Pushbullet.Types.Misc import Network.Pushbullet.Types.Status import Network.Pushbullet.Types.Time import Network.Pushbullet.Types.User import Control.Applicative ( (<|>) ) import Data.Aeson import Data.Text ( Text ) -- | A push. We reuse the same datatype for representing new pushes and -- existing pushes. The 'EqT' type family is used to enable fields selectively -- according to whether we're making a new push or representing an existing -- one. data Push (s :: Status) = Push { pushData :: !(PushData s) , pushSourceDevice :: !(Maybe DeviceId) , pushTarget :: !(PushTarget s) , pushGuid :: !(Maybe Guid) , pushId :: !(EqT 'Existing s PushId) , pushActive :: !(EqT 'Existing s Bool) , pushCreated :: !(EqT 'Existing s PushbulletTime) , pushModified :: !(EqT 'Existing s PushbulletTime) , pushDismissed :: !(EqT 'Existing s Bool) , pushDirection :: !(EqT 'Existing s PushDirection) , pushSender :: !(EqT 'Existing s UserId) , pushSenderEmail :: !(EqT 'Existing s EmailAddress) , pushSenderEmailNormalized :: !(EqT 'Existing s EmailAddress) , pushSenderName :: !(EqT 'Existing s Name) , pushReceiver :: !(EqT 'Existing s UserId) , pushReceiverEmail :: !(EqT 'Existing s EmailAddress) , pushReceiverEmailNormalized :: !(EqT 'Existing s EmailAddress) , pushOrigin :: !(EqT 'Existing s (Maybe PushOrigin)) } -- | Unique identifier for a push. newtype PushId = PushId Text deriving (Eq, FromJSON, Show, ToJSON) -- | The direction of a push. data PushDirection = SelfPush | OutgoingPush | IncomingPush deriving (Eq, Ord, Show) -- | The target of a push. data PushTarget (s :: Status) where ToAll :: PushTarget 'New ToDevice :: !DeviceId -> PushTarget 'New ToEmail :: !EmailAddress -> PushTarget 'New ToChannel :: !ChannelTag -> PushTarget 'New ToClient :: !ClientId -> PushTarget 'New SentBroadcast :: PushTarget 'Existing SentToDevice :: !DeviceId -> PushTarget 'Existing -- | The actual contents of a push. data PushData (s :: Status) = NotePush { pushTitle :: !Text , pushBody :: !Text } | LinkPush { pushTitle :: !Text , pushBody :: !Text , pushUrl :: !Url } | FilePush { pushBody :: !Text , pushFileName :: !Text , pushFileType :: !MimeType , pushFileUrl :: !Url , pushFileTitle :: !(EqT 'Existing s Text) , pushImageUrl :: !(EqT 'Existing s (Maybe Url)) , pushImageWidth :: !(EqT 'Existing s (Maybe Int)) , pushImageHeight :: !(EqT 'Existing s (Maybe Int)) } -- | The origin of a push. data PushOrigin = FromClient !ClientId | FromChannel !ChannelId deriving (Eq, Show) -- | A newtype wrapper for a list of existing pushes. We need this to get a -- nonstandard 'FromJSON' instance for the list, because Pushbullet gives us -- the list wrapped in a trivial object with one key. newtype ExistingPushes = ExistingPushes [Push 'Existing] deriving (Eq, Show) -- | Constructs a new @Push@ with the source device and guid set to @Nothing@. simpleNewPush :: PushTarget 'New -> PushData 'New -> Push 'New simpleNewPush t d = Push { pushData = d , pushSourceDevice = Nothing , pushTarget = t , pushGuid = Nothing , pushId = () , pushActive = () , pushCreated = () , pushModified = () , pushDismissed = () , pushDirection = () , pushSender = () , pushSenderEmail = () , pushSenderEmailNormalized = () , pushSenderName = () , pushReceiver = () , pushReceiverEmail = () , pushReceiverEmailNormalized = () , pushOrigin = () } instance ToJSON PushDirection where toJSON SelfPush = String "self" toJSON OutgoingPush = String "outgoing" toJSON IncomingPush = String "incoming" instance FromJSON PushDirection where parseJSON (String s) = case s of "self" -> pure SelfPush "outgoing" -> pure OutgoingPush "incoming" -> pure IncomingPush _ -> fail "invalid direction string" parseJSON _ = fail "cannot parse push direction from non-string" deriving instance Eq (PushTarget s) deriving instance Show (PushTarget s) deriving instance Eq (PushData 'New) deriving instance Eq (PushData 'Existing) deriving instance Show (PushData 'New) deriving instance Show (PushData 'Existing) deriving instance Eq (Push 'New) deriving instance Eq (Push 'Existing) deriving instance Show (Push 'New) deriving instance Show (Push 'Existing) instance FromJSON (Push 'Existing) where parseJSON (Object o) = do pushType <- o .: "type" d <- case id @Text pushType of "note" -> pure NotePush <*> o .: "title" <*> o .: "body" "file" -> pure FilePush <*> o .: "body" <*> o .: "file_name" <*> o .: "file_type" <*> o .: "file_url" <*> o .: "file_title" <*> o .:? "image_url" <*> o .:? "image_width" <*> o .:? "image_height" "link" -> pure LinkPush <*> o .: "title" <*> o .: "body" <*> o .: "url" _ -> fail "unrecognized push type" client <- o .:? "client_iden" channel <- o .:? "channel_iden" let origin = (FromClient <$> client) <|> (FromChannel <$> channel) pure Push <*> pure d <*> o .:? "source_device_iden" <*> (maybe SentBroadcast SentToDevice <$> o .:? "target_device_iden") <*> o .:? "guid" <*> o .: "iden" <*> o .: "active" <*> o .: "created" <*> o .: "modified" <*> o .: "dismissed" <*> o .: "direction" <*> o .: "sender_iden" <*> o .: "sender_email" <*> o .: "sender_email_normalized" <*> o .: "sender_name" <*> o .: "receiver_iden" <*> o .: "receiver_email" <*> o .: "receiver_email_normalized" <*> pure origin parseJSON _ = fail "cannot parse push from non-object" instance FromJSON ExistingPushes where parseJSON (Object o) = ExistingPushes <$> o .: "pushes" parseJSON _ = fail "cannot parse existing pushes from non-object" instance ToJSON (Push 'New) where toJSON Push{..} = object (concat pieces) where pieces = [ [ "source_device_iden" .= pushSourceDevice , "guid" .= pushGuid ] , target , thePushData ] target = case pushTarget of ToAll -> [] ToDevice d -> [ "device_iden" .= d ] ToEmail email -> [ "email" .= email ] ToChannel tag -> [ "channel_tag" .= tag ] ToClient client -> [ "client_iden" .= client ] thePushData = case pushData of NotePush{..} -> [ "type" .= id @Text "note" , "title" .= pushTitle , "body" .= pushBody ] LinkPush{..} -> [ "type" .= id @Text "link" , "title" .= pushTitle , "body" .= pushBody , "url" .= pushUrl ] FilePush{..} -> [ "type" .= id @Text "file" , "body" .= pushBody , "file_name" .= pushFileName , "file_type" .= pushFileType , "file_url" .= pushFileUrl ]