{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} module Facebook.Graph ( getObject , postObject , deleteObject , searchObjects , ( #= ) , SimpleType(..) , Place(..) , Location(..) , GeoCoordinates(..) , Tag(..) ) where #if __GLASGOW_HASKELL__ <= 784 import Control.Applicative #endif import Control.Monad (mzero) import Data.ByteString.Char8 (ByteString) import Data.Int (Int8, Int16, Int32, Int64) import Data.List (intersperse) import Data.Text (Text) import Data.Typeable (Typeable) import Data.Word (Word, Word8, Word16, Word32, Word64) #if MIN_VERSION_time(1,5,0) import Data.Time (defaultTimeLocale) #else import System.Locale (defaultTimeLocale) #endif import qualified Control.Monad.Trans.Resource as R import qualified Data.Aeson as A #if MIN_VERSION_aeson(1,0,0) import Data.Aeson.Text (encodeToTextBuilder) #else import Data.Aeson.Encode (encodeToTextBuilder) #endif import qualified Data.ByteString.Char8 as B import qualified Data.Text.Encoding as TE import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Builder as TLB import qualified Data.Time as TI import qualified Network.HTTP.Conduit as H import qualified Network.HTTP.Types as HT import Facebook.Auth import Facebook.Base import Facebook.Monad import Facebook.Types import Facebook.Pager -- | Make a raw @GET@ request to Facebook's Graph API. getObject :: (R.MonadResource m, R.MonadUnliftIO m, R.MonadThrow m, A.FromJSON a) => Text -- ^ Path (should begin with a slash @\/@) -> [Argument] -- ^ Arguments to be passed to Facebook -> Maybe (AccessToken anyKind) -- ^ Optional access token -> FacebookT anyAuth m a getObject path query mtoken = runResourceInFb $ asJson =<< fbhttp =<< fbreq path mtoken query -- | Make a raw @POST@ request to Facebook's Graph API. postObject :: (R.MonadResource m, R.MonadUnliftIO m, R.MonadThrow m, A.FromJSON a) => Text -- ^ Path (should begin with a slash @\/@) -> [Argument] -- ^ Arguments to be passed to Facebook -> AccessToken anyKind -- ^ Access token -> FacebookT Auth m a postObject = methodObject HT.methodPost -- | Make a raw @DELETE@ request to Facebook's Graph API. deleteObject :: (R.MonadResource m, R.MonadUnliftIO m, R.MonadThrow m, A.FromJSON a) => Text -- ^ Path (should begin with a slash @\/@) -> [Argument] -- ^ Arguments to be passed to Facebook -> AccessToken anyKind -- ^ Access token -> FacebookT Auth m a deleteObject = methodObject HT.methodDelete -- | Helper function used by 'postObject' and 'deleteObject'. methodObject :: (R.MonadResource m, R.MonadUnliftIO m, R.MonadThrow m, A.FromJSON a) => HT.Method -> Text -- ^ Path (should begin with a slash @\/@) -> [Argument] -- ^ Arguments to be passed to Facebook -> AccessToken anyKind -- ^ Access token -> FacebookT Auth m a methodObject method path query token = runResourceInFb $ do req <- fbreq path (Just token) query asJson =<< fbhttp req { H.method = method } -- | Make a raw @GET@ request to the /search endpoint of Facebook’s -- Graph API. Returns a raw JSON 'A.Value'. searchObjects :: (R.MonadResource m, R.MonadUnliftIO m, R.MonadThrow m, A.FromJSON a) => Text -- ^ A Facebook object type to search for -> Text -- ^ The keyword to search for -> [Argument] -- ^ Additional arguments to pass -> Maybe UserAccessToken -- ^ Optional access token -> FacebookT anyAuth m (Pager a) searchObjects objectType keyword query = getObject "/search" query' where query' = ("q" #= keyword) : ("type" #= objectType) : query ---------------------------------------------------------------------- -- | Create an 'Argument' with a 'SimpleType'. See the docs on -- 'createAction' for an example. ( #= ) :: SimpleType a => ByteString -> a -> Argument p #= v = (p, encodeFbParam v) -- | Class for data types that may be represented as a Facebook -- simple type. (see -- ). class SimpleType a where encodeFbParam :: a -> B.ByteString -- | Facebook's simple type @Boolean@. instance SimpleType Bool where encodeFbParam b = if b then "1" else "0" -- | Facebook's simple type @DateTime@ with only the date. instance SimpleType TI.Day where encodeFbParam = B.pack . TI.formatTime defaultTimeLocale "%Y-%m-%d" -- | Facebook's simple type @DateTime@. instance SimpleType TI.UTCTime where encodeFbParam = B.pack . TI.formatTime defaultTimeLocale "%Y%m%dT%H%MZ" -- | Facebook's simple type @DateTime@. instance SimpleType TI.ZonedTime where encodeFbParam = encodeFbParam . TI.zonedTimeToUTC -- @Enum@ doesn't make sense to support as a Haskell data type. -- | Facebook's simple type @Float@ with less precision than supported. instance SimpleType Float where encodeFbParam = showBS -- | Facebook's simple type @Float@. instance SimpleType Double where encodeFbParam = showBS -- | Facebook's simple type @Integer@. instance SimpleType Int where encodeFbParam = showBS -- | Facebook's simple type @Integer@. instance SimpleType Word where encodeFbParam = showBS -- | Facebook's simple type @Integer@. instance SimpleType Int8 where encodeFbParam = showBS -- | Facebook's simple type @Integer@. instance SimpleType Word8 where encodeFbParam = showBS -- | Facebook's simple type @Integer@. instance SimpleType Int16 where encodeFbParam = showBS -- | Facebook's simple type @Integer@. instance SimpleType Word16 where encodeFbParam = showBS -- | Facebook's simple type @Integer@. instance SimpleType Int32 where encodeFbParam = showBS -- | Facebook's simple type @Integer@. instance SimpleType Word32 where encodeFbParam = showBS -- | Facebook's simple type @Integer@. instance SimpleType Int64 where encodeFbParam = showBS -- | Facebook's simple type @Integer@. instance SimpleType Word64 where encodeFbParam = showBS -- | Facebook's simple type @String@. instance SimpleType Text where encodeFbParam = TE.encodeUtf8 -- | Facebook's simple type @String@. instance SimpleType ByteString where encodeFbParam = id -- | An object's 'Id' code. instance SimpleType Id where encodeFbParam = TE.encodeUtf8 . idCode -- | 'Permission' is a @newtype@ of 'Text' instance SimpleType Permission where encodeFbParam = encodeFbParam . unPermission -- | A comma-separated list of simple types. This definition -- doesn't work everywhere, just for a few combinations that -- Facebook uses (e.g. @[Int]@). Also, encoding a list of lists -- is the same as encoding the concatenation of all lists. In -- other words, this instance is here more for your convenience -- than to make sure your code is correct. instance SimpleType a => SimpleType [a] where encodeFbParam = B.concat . intersperse "," . map encodeFbParam showBS :: Show a => a -> B.ByteString showBS = B.pack . show ---------------------------------------------------------------------- -- | Information about a place. This is not a Graph Object, -- instead it's just a field of a Object. (Not to be confused -- with the @Page@ object.) data Place = Place { placeId :: Id -- ^ @Page@ ID. , placeName :: Maybe Text -- ^ @Page@ name. , placeLocation :: Maybe Location } deriving (Eq, Ord, Show, Read, Typeable) instance A.FromJSON Place where parseJSON (A.Object v) = Place <$> v A..: "id" <*> v A..:? "name" <*> v A..:? "location" parseJSON _ = mzero -- | A geographical location. data Location = Location { locationStreet :: Maybe Text , locationCity :: Maybe Text , locationState :: Maybe Text , locationCountry :: Maybe Text , locationZip :: Maybe Text , locationCoords :: Maybe GeoCoordinates } deriving (Eq, Ord, Show, Read, Typeable) instance A.FromJSON Location where parseJSON obj@(A.Object v) = Location <$> v A..:? "street" <*> v A..:? "city" <*> v A..:? "state" <*> v A..:? "country" <*> v A..:? "zip" <*> A.parseJSON obj parseJSON _ = mzero -- | Geographical coordinates. data GeoCoordinates = GeoCoordinates { latitude :: !Double , longitude :: !Double } deriving (Eq, Ord, Show, Read, Typeable) instance A.FromJSON GeoCoordinates where parseJSON (A.Object v) = GeoCoordinates <$> v A..: "latitude" <*> v A..: "longitude" parseJSON _ = mzero instance SimpleType GeoCoordinates where encodeFbParam c = let obj = A.object ["latitude" A..= latitude c, "longitude" A..= longitude c] toBS = TE.encodeUtf8 . TL.toStrict . TLB.toLazyText . encodeToTextBuilder in toBS obj -- | A tag (i.e. \"I'll /tag/ you on my post\"). data Tag = Tag { tagId :: Id -- ^ Who is tagged. , tagName :: Text -- ^ Name of the tagged person. } deriving (Eq, Ord, Show, Read, Typeable) instance A.FromJSON Tag where parseJSON (A.Object v) = Tag <$> v A..: "id" <*> v A..: "name" parseJSON _ = mzero