packages feed

fb 0.11 → 0.11.1

raw patch · 6 files changed

+263/−6 lines, 6 filesdep +base16-bytestringdep ~hspecPVP ok

version bump matches the API change (PVP)

Dependencies added: base16-bytestring

Dependency ranges changed: hspec

API changes (from Hackage documentation)

+ Facebook: ErrorsRTUO :: RealTimeUpdateObject
+ Facebook: OtherRTUO :: Text -> RealTimeUpdateObject
+ Facebook: PageRTUO :: RealTimeUpdateObject
+ Facebook: PermissionsRTUO :: RealTimeUpdateObject
+ Facebook: RealTimeUpdateNotification :: RealTimeUpdateObject -> [a] -> RealTimeUpdateNotification a
+ Facebook: RealTimeUpdateNotificationUserEntry :: Id -> [RealTimeUpdateField] -> Integer -> RealTimeUpdateNotificationUserEntry
+ Facebook: RealTimeUpdateSubscription :: RealTimeUpdateObject -> RealTimeUpdateUrl -> [RealTimeUpdateField] -> Bool -> RealTimeUpdateSubscription
+ Facebook: UserRTUO :: RealTimeUpdateObject
+ Facebook: data RealTimeUpdateNotification a
+ Facebook: data RealTimeUpdateNotificationUserEntry
+ Facebook: data RealTimeUpdateObject
+ Facebook: data RealTimeUpdateSubscription
+ Facebook: getRealTimeUpdateNotifications :: (Monad m, FromJSON a) => ByteString -> ByteString -> FacebookT Auth m (Maybe (RealTimeUpdateNotification a))
+ Facebook: listSubscriptions :: (MonadResource m, MonadBaseControl IO m) => AppAccessToken -> FacebookT Auth m [RealTimeUpdateSubscription]
+ Facebook: modifySubscription :: (MonadResource m, MonadBaseControl IO m) => RealTimeUpdateObject -> [RealTimeUpdateField] -> RealTimeUpdateUrl -> RealTimeUpdateToken -> AppAccessToken -> FacebookT Auth m ()
+ Facebook: rtunEntries :: RealTimeUpdateNotification a -> [a]
+ Facebook: rtunObject :: RealTimeUpdateNotification a -> RealTimeUpdateObject
+ Facebook: rtuneChangedFields :: RealTimeUpdateNotificationUserEntry -> [RealTimeUpdateField]
+ Facebook: rtuneTime :: RealTimeUpdateNotificationUserEntry -> Integer
+ Facebook: rtuneUserId :: RealTimeUpdateNotificationUserEntry -> Id
+ Facebook: rtusActive :: RealTimeUpdateSubscription -> Bool
+ Facebook: rtusCallbackUrl :: RealTimeUpdateSubscription -> RealTimeUpdateUrl
+ Facebook: rtusFields :: RealTimeUpdateSubscription -> [RealTimeUpdateField]
+ Facebook: rtusObject :: RealTimeUpdateSubscription -> RealTimeUpdateObject
+ Facebook: type RealTimeUpdateField = ByteString
+ Facebook: type RealTimeUpdateToken = ByteString
+ Facebook: type RealTimeUpdateUrl = Text
+ Facebook: verifyRealTimeUpdateNotifications :: Monad m => ByteString -> ByteString -> FacebookT Auth m (Maybe ByteString)

Files

fb.cabal view
@@ -1,5 +1,5 @@ name:              fb-version:           0.11+version:           0.11.1 license:           BSD3 license-file:      LICENSE author:            Felipe Lessa@@ -54,6 +54,7 @@     Facebook.Object.User     Facebook.Object.Page     Facebook.OpenGraph+    Facebook.RealTime   build-depends:       base               >= 4       && < 5     , lifted-base        >= 0.1     && < 0.2@@ -68,6 +69,7 @@     , attoparsec         >= 0.10    && < 0.11     , attoparsec-conduit >= 0.5     && < 0.6     , aeson              >= 0.5     && < 0.7+    , base16-bytestring  == 0.1.*     , base64-bytestring  >= 0.1.1   && < 0.2     , time               >= 1.2     && < 1.5     , old-locale@@ -103,7 +105,7 @@       -- Test-only dependencies     , HUnit     , QuickCheck-    , hspec >= 0.9.1 && < 1.3+    , hspec == 1.3.*     , fb   extensions:     TypeFamilies
src/Facebook.hs view
@@ -58,6 +58,21 @@     , (#=)     , SimpleType(..) +      -- * Real-time update notifications+      -- ** Subscriptions+    , modifySubscription+    , listSubscriptions+    , RealTimeUpdateObject(..)+    , RealTimeUpdateField+    , RealTimeUpdateUrl+    , RealTimeUpdateToken+    , RealTimeUpdateSubscription(..)+      -- ** Notifications+    , verifyRealTimeUpdateNotifications+    , getRealTimeUpdateNotifications+    , RealTimeUpdateNotification(..)+    , RealTimeUpdateNotificationUserEntry(..)+       -- * Raw access to the Graph API     , getObject     , postObject@@ -81,3 +96,4 @@ import Facebook.Object.Page import Facebook.Object.User import Facebook.OpenGraph+import Facebook.RealTime
src/Facebook/Graph.hs view
@@ -28,8 +28,7 @@ import Facebook.Base  --- | Make a raw @GET@ request to Facebook's Graph API.  Returns a--- raw JSON 'A.Value'.+-- | Make a raw @GET@ request to Facebook's Graph API. getObject :: (C.MonadResource m, MonadBaseControl IO m, A.FromJSON a) =>              ByteString     -- ^ Path (should begin with a slash @\/@)           -> [Argument]     -- ^ Arguments to be passed to Facebook@@ -40,8 +39,7 @@     asJson =<< fbhttp =<< fbreq path mtoken query  --- | Make a raw @POST@ request to Facebook's Graph API.  Returns--- a raw JSON 'A.Value'.+-- | Make a raw @POST@ request to Facebook's Graph API. postObject :: (C.MonadResource m, MonadBaseControl IO m, A.FromJSON a) =>               ByteString          -- ^ Path (should begin with a slash @\/@)            -> [Argument]          -- ^ Arguments to be passed to Facebook
src/Facebook/OpenGraph.hs view
@@ -219,6 +219,9 @@ -- | 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
+ src/Facebook/RealTime.hs view
@@ -0,0 +1,231 @@+{-# LANGUAGE FlexibleContexts, OverloadedStrings #-}+module Facebook.RealTime+    ( RealTimeUpdateObject(..)+    , RealTimeUpdateField+    , RealTimeUpdateUrl+    , RealTimeUpdateToken+    , modifySubscription+    , RealTimeUpdateSubscription(..)+    , listSubscriptions+    , verifyRealTimeUpdateNotifications+    , getRealTimeUpdateNotifications+    , RealTimeUpdateNotification(..)+    , RealTimeUpdateNotificationUserEntry(..)+    ) where++import Control.Applicative ((<$>), (<*>))+import Control.Monad.Trans.Control (MonadBaseControl)+import Control.Monad (liftM, mzero)+import Data.ByteString.Char8 (ByteString)+import Data.Text (Text)+import Data.Typeable (Typeable)++import qualified Crypto.Classes as Crypto+import qualified Crypto.HMAC as Crypto+import qualified Crypto.Hash.SHA1 as SHA1+import qualified Data.Aeson as A+import qualified Data.ByteString.Base16 as Base16+import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.Conduit as C+import qualified Data.Text.Encoding as TE++import Facebook.Types+import Facebook.Monad+import Facebook.Graph+import Facebook.OpenGraph+++-- | The type of objects that a real-time update refers to.+data RealTimeUpdateObject =+    UserRTUO+  | PermissionsRTUO+  | PageRTUO+  | ErrorsRTUO+  | OtherRTUO Text+    deriving (Eq, Ord, Show, Typeable)++rtuoToBS :: RealTimeUpdateObject -> ByteString+rtuoToBS (UserRTUO)        = "user"+rtuoToBS (PermissionsRTUO) = "permissions"+rtuoToBS (PageRTUO)        = "page"+rtuoToBS (ErrorsRTUO)      = "errors"+rtuoToBS (OtherRTUO other) = TE.encodeUtf8 other++instance A.FromJSON RealTimeUpdateObject where+  parseJSON (A.String "user")        = return UserRTUO+  parseJSON (A.String "permissions") = return PermissionsRTUO+  parseJSON (A.String "page")        = return PageRTUO+  parseJSON (A.String "errors")      = return ErrorsRTUO+  parseJSON (A.String other)         = return (OtherRTUO other)+  parseJSON _ = mzero++instance A.ToJSON RealTimeUpdateObject where+  toJSON = A.String . TE.decodeUtf8 . rtuoToBS+++-- | A field of a 'RealTimeUpdateObject' that you would like to+-- receive notifications when changed.+type RealTimeUpdateField = ByteString+++-- | The URL on your server that will receive the real-time+-- updates.  Please refer to Facebook's documentation in order to+-- see what this URL needs to implement.+type RealTimeUpdateUrl = Text+++-- | A token that is sent back by Facebook's servers to your+-- server in order to verify that you really were trying to+-- modify your subscription.+type RealTimeUpdateToken = ByteString+++-- | Add or modify a subscription for real-time updates.  If+-- there were no previous subscriptions for the given+-- 'RealTimeUpdateObject', then a new subscription is created.+-- If there was any previous subscription for the given+-- 'RealTimeUpdateObject', it's overriden by this one (even if+-- the other subscription had a different callback URL).+modifySubscription :: (C.MonadResource m, MonadBaseControl IO m) =>+                      RealTimeUpdateObject+                      -- ^ Type of objects whose subscription you+                      -- and to add or modify.+                   -> [RealTimeUpdateField]+                      -- ^ Fields that you are interested in+                      -- receiving updates.+                   -> RealTimeUpdateUrl+                      -- ^ Your callback URL.+                   -> RealTimeUpdateToken+                      -- ^ A verification token.+                   -> AppAccessToken+                      -- ^ Access token for your app.+                   -> FacebookT Auth m ()+modifySubscription object fields callbackUrl verifyToken apptoken = do+  path <- getSubscriptionsPath+  let args = [ "object"       #= rtuoToBS object+             , "fields"       #= fields+             , "callback_url" #= callbackUrl+             , "verify_token" #= verifyToken+             ]+  postObject path args apptoken+++-- | (Internal)  Get the subscription's path.+getSubscriptionsPath :: Monad m => FacebookT Auth m ByteString+getSubscriptionsPath = do+  creds <- getCreds+  return $ B.concat ["/", appId creds, "/subscriptions"]+++-- | Information returned by Facebook about a real-time update+-- notification subscription.+data RealTimeUpdateSubscription =+  RealTimeUpdateSubscription {+    rtusObject      :: RealTimeUpdateObject+  , rtusCallbackUrl :: RealTimeUpdateUrl+  , rtusFields      :: [RealTimeUpdateField]+  , rtusActive      :: Bool+  } deriving (Eq, Ord, Show, Typeable)++instance A.FromJSON RealTimeUpdateSubscription where+  parseJSON (A.Object v) =+    RealTimeUpdateSubscription+      <$> v A..: "object"+      <*> v A..: "callback_url"+      <*> v A..: "fields"+      <*> v A..: "active"+  parseJSON _ = mzero+++-- | List current real-time update subscriptions.+listSubscriptions ::+  (C.MonadResource m, MonadBaseControl IO m) =>+  AppAccessToken -> FacebookT Auth m [RealTimeUpdateSubscription]+listSubscriptions apptoken = do+  path <- getSubscriptionsPath+  FQLResult ret <- getObject path [] (Just apptoken)+  return ret+++-- | Verifies the input's authenticity (i.e. it comes from+-- Facebook) and integrity by calculating its HMAC-SHA1 (using+-- your application secret as the key) and verifying that it+-- matches the value from the HTTP request's @X-Hub-Signature@+-- header's value.  If it's not valid, @Nothing@ is returned,+-- otherwise @Just data@ is returned where @data@ is the original+-- data.+verifyRealTimeUpdateNotifications ::+     Monad m =>+     ByteString+     -- ^ @X-Hub-Signature@ HTTP header's value.+  -> L.ByteString+     -- ^ Request body with JSON-encoded notifications.+  -> FacebookT Auth m (Maybe L.ByteString)+verifyRealTimeUpdateNotifications sig body = do+  creds <- getCreds+  let key :: Crypto.MacKey SHA1.Ctx SHA1.SHA1+      key = Crypto.MacKey (appSecret creds)+      hash = Crypto.hmac key body+      expected = "sha1=" <> Base16.encode (Crypto.encode hash)+  return $! if sig `Crypto.constTimeEq` expected then Just body else Nothing+++-- | Same as 'verifyRealTimeUpdateNotifications' but also parses+-- the response as JSON.  Returns @Nothing@ if either the+-- signature is invalid or the data can't be parsed (use+-- 'verifyRealTimeUpdateNotifications' if you need to distinguish+-- between these two error conditions).+getRealTimeUpdateNotifications ::+     (Monad m, A.FromJSON a) =>+     ByteString+     -- ^ @X-Hub-Signature@ HTTP header's value.+  -> L.ByteString+     -- ^ Request body with JSON-encoded notifications.+  -> FacebookT Auth m (Maybe (RealTimeUpdateNotification a))+getRealTimeUpdateNotifications =+  (liftM (>>= A.decode) .) . verifyRealTimeUpdateNotifications+++-- | When data changes and there's a valid subscription, Facebook+-- will @POST@ to your 'RealTimeUpdateUrl' with a JSON-encoded+-- object containing the notifications.  A+-- 'RealTimeUpdateNotification a' represents such object where+-- 'a' is type of the entries (e.g.,+-- 'RealTimeUpdateNotificationUserEntry').+--+-- If you have a single 'RealTimeUpdateUrl' for different kinds+-- of notifications, you may parse a @RealTimeUpdateNotification+-- 'A.Value'@ and then manually parse the 'A.Value' depending on+-- the value of 'rtunObject'.+--+-- We recommend using 'getRealTimeUpdateNotifications'.+data RealTimeUpdateNotification a =+  RealTimeUpdateNotification {+    rtunObject  :: RealTimeUpdateObject+  , rtunEntries :: [a]+  } deriving (Eq, Ord, Show, Typeable)++instance A.FromJSON a => A.FromJSON (RealTimeUpdateNotification a) where+  parseJSON (A.Object v) =+    RealTimeUpdateNotification+      <$> v A..: "object"+      <*> v A..: "entry"+  parseJSON _ = mzero+++-- | A notification for the 'UserRTUO' object.+data RealTimeUpdateNotificationUserEntry =+  RealTimeUpdateNotificationUserEntry {+    rtuneUserId        :: Id+  , rtuneChangedFields :: [RealTimeUpdateField]+  , rtuneTime          :: Integer+  } deriving (Eq, Ord, Show, Typeable)++instance A.FromJSON RealTimeUpdateNotificationUserEntry where+  parseJSON (A.Object v) =+    RealTimeUpdateNotificationUserEntry+      <$> v A..: "uid"+      <*> v A..: "changed_fields"+      <*> v A..: "time"+  parseJSON _ = mzero
tests/runtests.hs view
@@ -167,6 +167,13 @@         r <- FB.fqlQuery "SELECT name FROM page WHERE page_id = 20531316728" Nothing         r &?= FB.FQLResult [PageName "Facebook"] +  describe' "listSubscriptions" $ do+    it "returns something" $ do+      runAuth $ do+        token <- FB.getAppAccessToken+        val   <- FB.listSubscriptions token+        length val `seq` return ()+ newtype PageName = PageName Text deriving (Eq, Show) instance A.FromJSON PageName where   parseJSON (A.Object v) = PageName <$> (v A..: "name")