{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, FlexibleContexts, ScopedTypeVariables, TemplateHaskell, TypeFamilies, TypeSynonymInstances, UndecidableInstances #-}
{-# OPTIONS_GHC -F -pgmFtrhsx #-}
module Happstack.FBConnect where

import Control.Applicative(Applicative((<*>), pure),(<$>))
import Control.Arrow(first, second)
import Control.Monad (ap, liftM, when)
import Control.Monad.Trans (MonadTrans,MonadIO,lift, liftIO)
import Control.Monad.Reader (asks)
import Control.Monad.State (MonadState,StateT,evalStateT,get, put)
import qualified Data.ByteString.Char8 as P
import qualified Data.ByteString.Lazy.Char8 as L
import qualified Data.ByteString.Lazy.UTF8 as L
import Data.Char
import Data.Either (partitionEithers)
import Data.Function (on)
import Data.Ix(Ix)
import Data.List
import qualified Data.Map as Map
import Data.Generics (Data,Typeable)
import Data.List (isPrefixOf, sortBy)
import Data.Maybe (catMaybes, isJust, fromJust)
import Data.Time.Clock.POSIX (POSIXTime)
import Debug.Trace(trace, traceShow)
import Happstack.Data (Default(..), Version(..),deriveAll, deriveNewData, deriveSerialize)
import Happstack.State (Proxy(..))
import Happstack.Server (ServerPartT(ServerPartT), ToMessage(toMessage, toContentType), 
                     WebT(WebT), Response, toResponse, withDataFn, Method(..), mapServerPartT,
                     seeOther, Cookie(..), RqData, lookCookieValue, mkCookie, addCookie)
import Happstack.Crypto.MD5 ( md5, stringMD5)
import HSP
import HSP.Identity (evalIdentity)
import HSP (HSPT,XML,XMLMetaData,evalHSPT,renderXML)
import qualified Network.HTTP as HTTP
import Network.Browser (Form(..),formToRequest, request, browse)
import Network.URI (URI,parseURI,uriToString)
import System.Time (ClockTime(TOD),getClockTime)
import Text.RJson -- FIXME: use different JSON library?

import Happstack.Facebook.Common (FacebookConfig(..),ApiKey(..),AppSecret(..),AppId(..))

$(deriveAll  [''Eq, ''Ord, ''Read, ''Show, ''Ix, ''Default]
  [d| newtype User = User { fbUid :: Integer } |])

instance Version User
$(deriveSerialize ''User)

$(deriveAll  [''Eq, ''Ord, ''Read, ''Show, ''Ix, ''Default]
  [d| newtype PhotoId = PhotoId { pid :: Integer } |])

$(deriveAll  [''Eq, ''Ord, ''Read, ''Show, ''Ix, ''Default]
  [d| newtype AlbumId = AlbumId { aid :: Integer } |])

type SessionKey = String

-- |This data type holds all the informaton that facebook pass along with the request
-- http://wiki.developers.facebook.com/index.php/Your_callback_page_and_you
data FacebookData =
    FacebookData 
    { _fbc_cookies           :: [(String,Cookie)]
    , _fbc_sig_expires       :: Maybe POSIXTime
    , _fbc_sig_session_key   :: Maybe SessionKey
    , _fbc_sig_ss            :: Maybe String
    , _fbc_user              :: Maybe User
    , _fbc_sig               :: Maybe String
    , _fbc_valid_sig         :: Bool
    }
    deriving (Eq, Show)

-- |function to read some FacebookData from the Facebook environment
fbd :: (FacebookData -> a) -> Facebook a
fbd select = 
    do fs <- getFBS
       return (select (fbData fs))

-- ** convenience functions for getting information from the FacebookData
-- |when this session key expires
--  0 == never
--  otherwise, time in seconds since epoch`
-- NOTE: only available if the user is logged in
fb_sig_expires :: Facebook (Maybe POSIXTime)
fb_sig_expires = fbd _fbc_sig_expires

-- |session key
-- NOTE: only available if the user is logged in
fb_sig_session_key :: Facebook (Maybe SessionKey)
fb_sig_session_key = fbd _fbc_sig_session_key

fb_sig_ss :: Facebook (Maybe String)
fb_sig_ss = fbd _fbc_sig_ss

fb_user :: Facebook (Maybe User)
fb_user = fbd _fbc_user

-- |the signature for the request
fb_sig :: Facebook (Maybe String)
fb_sig = fbd _fbc_sig

-- |helper function
lookupBool :: String -> [(String, String)] -> Bool
lookupBool key assoc =
    case lookup key assoc of
      Nothing -> error ("required key " ++ key ++ " was not found.")
      (Just "0") -> False
      (Just "1") -> False
      (Just str) -> error ("key " ++ key ++ " has unparsable value " ++ str)

-- |our facebook code will live in the Facebook monad. Currently this
-- monad just provides some environment data. The Facebook monad lets
-- us embed Facebook Markup Language using literal XML via HSP.
type Facebook = FacebookT IO

instance Applicative Facebook where
    pure = return
    (<*>) = ap

newtype FacebookT m a =
    FacebookT { unFacebookT :: StateT FacebookState m a }
              deriving (Functor, Monad, MonadIO, MonadTrans)

-- |the State that lives in the facebook monad
data FacebookState 
    = FacebookState { fbConfig :: FacebookConfig -- our api id, etc,
                    , fbData   :: FacebookData   -- user specific information that facebook provides
                    }

withFacebook :: FacebookConfig -> ServerPartT Facebook a -> ServerPartT IO a
withFacebook config sp = withFacebookData config $ \fbd -> withFacebook' config fbd sp

-- todo: add code to validate the sent fb_sig
withFacebookData :: (Monad m) => FacebookConfig -> (FacebookData -> ServerPartT m r) -> ServerPartT m r
withFacebookData (FacebookConfig (ApiKey api) s@(AppSecret secret) _ _ _) f =
  withDataFn buildFBCData f
  where read' str = case reads str of
                         [(a,[])] -> a
                         r -> error ("Failed to read " ++ str ++ " got " ++ show r)
        readTime :: String -> POSIXTime
        readTime str = realToFrac (read' str :: Double)
        lookupString :: String -> RqData (Maybe String)
        lookupString k = do
          cookies <- asks snd
          return $ fmap cookieValue $ lookup (map toLower (api++k)) cookies
        buildFBCData :: RqData FacebookData
        buildFBCData = do
          expires <- lookupString "_expires"
          sesKey <- lookupString "_session_key"
          ss <- lookupString "_ss"
          sig <- lookupString ""
          user <- lookupString "_user"
          cookies <- asks snd
          let fbcCookies = map (\(k,v) -> (drop ((length api)+1) k, v)) $ filter (isPrefixOf (api++"_") . fst) cookies
          return $ FacebookData
            fbcCookies (fmap readTime expires)
            sesKey ss (fmap (User . read) user)
            sig (validateSignature s (maybe "" id sig) fbcCookies)

withFacebook' :: FacebookConfig -> FacebookData -> ServerPartT (FacebookT IO) a -> ServerPartT IO a
withFacebook' config facebookData sp =
    mapServerPartT doHSPT sp
    where
      doHSPT hspt =
          evalStateT (unFacebookT hspt) (FacebookState config facebookData)

type FacebookMethodString = String

newtype CallId = CallId Integer
    deriving (Enum, Eq, Integral, Num, Ord, Read, Real, Show, Ix)

getFBS :: (MonadState a (FacebookT IO)) => Facebook a
getFBS = get

--instance (Monad m) => Applicative (StateT s m) where
--    pure = return
--    (<*>) = ap

-- normally we would just derive this, but trhsx can't handle it
-- (haddock used to break as well, I wonder if that is fixed now?)
instance (Monad m) => MonadState FacebookState (FacebookT m) where
    get = FacebookT get
    put s = FacebookT (put s)

modifyFBS :: (MonadState s m) => (s -> s) -> FacebookT m s
modifyFBS f =
    do fbs <- FacebookT (lift get)
       FacebookT (lift (put (f fbs)))
       return fbs

callId :: Facebook CallId
callId = 
    do (TOD x y) <- liftIO getClockTime
       return (CallId (x + y))

-- |calculate the sig, and return the args in sorted order. The sig is *not* added to the args.
signature :: AppSecret -> [(String, String)] -> (String, [(String, String)])
signature (AppSecret appSecret) unsortedArgs =
    let args = sortBy (compare `on` fst) unsortedArgs
        argStr = concatMap (\(k,v) -> k ++ '=' : v) args
        sig = stringMD5 (md5 (L.pack (argStr ++ appSecret)))
    in (sig, args)

validateSignature :: AppSecret -> String -> [(String,Cookie)] -> Bool
validateSignature secret sig cookies = sig' == sig
  where sorted = sortBy (compare `on` fst) cookies
        dataStr = concatMap (\(k,v) -> k ++ '=' : (cookieValue v)) sorted
        sig' = stringMD5 (md5 (L.pack (dataStr++(unAppSecret secret))))                                                             

fbcLogout :: ServerPartT Facebook ()
fbcLogout = do
  fbs <- lift getFBS
  let clear c = do addCookie 0 ck
        where key = (unApiKey $ apiKey $ fbConfig fbs)++"_"++c
              ck = (mkCookie key "0")
  sequence $ map (clear.fst) $ _fbc_cookies $ fbData fbs
  return ()

{-
instance ToMessage (Maybe XMLMetaData, XML) where
    toContentType _ = P.pack "application/xml; charset=utf-8"
    toMessage (_,xml) = L.fromString (renderXML xml)

instance ToMessage XML where
    toContentType _ = P.pack "application/xml; charset=utf-8"
    toMessage xml = L.fromString (renderXML xml)
-}

newtype FbXML = FbXML { unFbXML :: XML }


-- FIXME: the <style> and <script> tags should be encoded like html not xhtml
instance ToMessage FbXML where
    toContentType _ = P.pack "application/xml; charset=utf-8"
    toMessage (FbXML xml) = L.fromString (renderXML xml)

instance ToMessage (Maybe XMLMetaData, FbXML) where
    toContentType _ = P.pack "application/xml; charset=utf-8"
    toMessage (_,FbXML xml) = L.fromString (renderXML xml)


facebook :: (MonadIO m, ToMessage a) =>
            FacebookConfig -> FacebookData -> FacebookT IO a -> m Response
facebook config fbData fb =
    do a <- liftIO $ evalStateT (unFacebookT fb) (FacebookState config fbData)
       return (toResponse a)


-- |redirect to login if no session key is found
requireLogin :: URI -- ^ where to go after the login
             -> ServerPartT Facebook XML -> ServerPartT Facebook XML
requireLogin next sp =
    do mSessionKey <- lift fb_sig_session_key
       if isJust mSessionKey
          then sp
          else do key <- unApiKey . apiKey . fbConfig <$> lift getFBS
                  let uri = "http://www.facebook.com/login.php?v=1.0&api_key=" ++ 
                            key ++"&next=" ++ uriToString id next "&canvas="
                  seeOther uri (evalIdentity <fb:redirect url=uri />)

-- * Utility Functions
{-
assocToJSON :: [(String, String)] -> JsonData
assocToJSON assoc = JDArray $ map toAssoc assoc
    where
      toAssoc (k, v) = JDObject (Map.singleton k (toJson v))
-}

assocToJSON :: [(String, String)] -> JsonData
assocToJSON assoc = JDObject $ Map.fromList (map (second toJson) assoc)

parseResponseBool :: String -> Either String Bool
parseResponseBool str =
    case fromJsonString [] str of
      (Right [b]) -> Right b
      (Left e)    -> Left e

parseUserIds :: String -> Either String [User]
parseUserIds str = 
    case partitionEithers $ map (fmap User . read') (commas str)  of
      ([], users) -> Right users
      (errors,_) -> Left $ show errors
    where
      commas :: String -> [String]
      commas = words . map (\c -> if (c==',') then ' ' else c)
      read' str = case reads str of
                    [(a,[])] -> Right a
                    r -> Left ("Failed to read " ++ str ++ " got " ++ show r)

toCommaList :: [String] -> String
toCommaList = intercalate ","

-- * API

type Parameters = [(String, String)]

buildRequest :: Parameters -> Facebook (CallId -> HTTP.Request String)
buildRequest parameters = 
    do fbConfig <- liftM fbConfig getFBS
       uid <- liftM (maybe "" (show . fbUid)) fb_user -- FIXME : not all calls require this
       return $ \cid ->
           let (sig, args) = signature (appSecret fbConfig) $
                      [ ("api_key", unApiKey $ apiKey fbConfig)
                      , ("call_id", show (toInteger cid))
                      , ("format","json")
                      , ("uid", uid)
                      , ("v","1.0")
                      ] ++ parameters
           in formToRequest (Form HTTP.POST fbRESTURI (args ++ [("sig",sig)]))
    where
      fbRESTURI :: URI
      fbRESTURI = fromJust $ parseURI "http://api.facebook.com/restserver.php"


execRequest :: (CallId -> HTTP.Request String) -> Facebook String
execRequest req =
    do cid <- callId
       (_uri, res) <- liftIO $ browse (request (req cid))
       let body = HTTP.rspBody res
       return body

class FacebookMethod method where
    type FacebookResponse method
    toParams :: method -> Facebook Parameters
    parseResponse :: Proxy method -> String -> Either String (FacebookResponse method)

class RequiresSession a

callMethod :: forall method. (FacebookMethod method) => method -> Facebook (FacebookResponse method)
callMethod method =
    do res <- callMethodE method
       case res of
         Left e -> error (show e)
         Right r -> return $ r

callMethodE :: forall method. (FacebookMethod method) => method -> Facebook (Either FacebookError (FacebookResponse method))
callMethodE method =
    do req <- buildRequest =<< toParams method
       liftIO (putStrLn $ "FB Request: " ++ (show $  req (CallId 0)))
       res <- execRequest req
       liftIO (putStrLn $ "FB Response: " ++ (show res))
       return $ parseResponse' (Proxy :: Proxy method) res 

data FacebookError
    = FacebookError
      { error_code :: Integer
      , error_msg  :: String
      , request_args :: Parameters
      }
    | ParseError String
      deriving (Eq, Ord, Read, Show)

parseResponse' :: (FacebookMethod method) => Proxy method -> String -> Either FacebookError (FacebookResponse method)
parseResponse' method responseString
    | "{\"error_code\":" `isPrefixOf` responseString =
        Left (parseError responseString)
    | otherwise = 
        case parseResponse method responseString of
          (Left str) -> Left (ParseError str)
          (Right r)  -> Right r

parseError :: String -> FacebookError
parseError responseString =
    case parseJsonString responseString of
      (Left e) -> ParseError e
      (Right (JDObject json)) -> 
          FacebookError { error_code = 
                            let (Just (JDNumber d))    = Map.lookup "error_code" json
                            in floor d
                        , error_msg = 
                            let (Just (JDString str))  = Map.lookup "error_msg" json
                            in str
                        , request_args =
                            let (Just (JDArray args)) = Map.lookup "request_args" json
                            in map fromKeyValue args
                        }
    where
      fromKeyValue :: JsonData -> (String, String)
      fromKeyValue (JDObject json) = (fromJD $ fromJust $ Map.lookup "key" json, fromJD $ fromJust $ Map.lookup "value" json)
      fromJD (JDString str) = str
      fromJD (JDNumber d) = show d
             
    
    
            
      
-- ** Status

data StatusSet = StatusSet String

-- according to the docs, you only need uid *or* session_key, but
-- never both. However, it did not work for me with out the session
-- key.
instance FacebookMethod StatusSet where
    type FacebookResponse StatusSet = String
    parseResponse _ = Right . id
    toParams (StatusSet status) =
        do sessionKey <- fb_sig_session_key
           return $ catMaybes $ [ Just ("method","Status.set")
                                , Just ("status", status)
                                , fmap (\key -> ("session_key", key)) sessionKey
                                ]

-- ** Feed

newtype BundleId
    = BundleId { unBundledId :: Integer }
      deriving (Data, Typeable, Eq, Ord, Read, Show, Ix)

instance ToJson BundleId where
    toJson (BundleId bid) = toJson bid

data StorySize
    = OneLine
    | Short
      deriving (Read, Show, Eq, Ord, Ix)

instance Enum StorySize where
    succ OneLine = Short
    succ Short = error "tried to take `succ' of maxBound" 
    pred OneLine = error "tried to take `pred` of minBound"
    pred Short = OneLine
    toEnum 1 = OneLine
    toEnum 2 = Short
    fromEnum OneLine = 1
    fromEnum Short   = 2

    enumFrom OneLine = [OneLine, Short]
    enumFrom Short =   [Short]

    enumFromTo from to = takeWhile (<= to) $ enumFrom from

-- |Deactivates a previously registered template bundle
data FeedDeactivateTemplateBundleById
    = FeedDeactivateTemplateBundleById BundleId

instance FacebookMethod FeedDeactivateTemplateBundleById where
    type FacebookResponse FeedDeactivateTemplateBundleById = Bool
    parseResponse _ = parseResponseBool
    toParams (FeedDeactivateTemplateBundleById (BundleId bid)) =
        return [ ("method","Feed.deactivateTemplateBundleById")
               , ("template_bundle_id", toJsonString bid)
               ]

-- data FeedGetRegisteredTemplateBundles
--    = FeedGetRegisteredTemplateBundles

-- instance FacebookMethod FeedGetRegisteredTemplateBundles where
--    type FacebookResponse FeedGetRegisteredTemplateBundles = [TemplateBundle]

data FeedPublishUserAction 
    = FeedPublishUserAction BundleId (Maybe StorySize) Parameters 

instance FacebookMethod FeedPublishUserAction where
    type FacebookResponse FeedPublishUserAction = String
    parseResponse _ = Right . id
    toParams (FeedPublishUserAction (BundleId bid) mStorySize templateData) =
        do sessionKey <- fb_sig_session_key
           return $ catMaybes $
               [ Just ("method", "Feed.publishUserAction")
               , Just ("template_bundle_id", show bid)
               , Just ("template_data", show $ assocToJSON templateData)
               , fmap (\key -> ("session_key", key)) sessionKey -- session key is required
               , fmap (\size -> ("story_size", show $ fromEnum size)) mStorySize
               ]

data ActionLink = ActionLink String String

actionLinkToJSON (ActionLink text uri) =
        JDObject $ Map.fromList [ ("text", (toJson text))
                                , ("href", (toJson uri))
                                ]


-- |FeedData is used to supply data for the feedStory/multiFeedStory callback
-- <http://wiki.developers.facebook.com/index.php/Feed_Forms>
data FeedData 
    = FeedData 
       { target :: FeedTarget 
       , template_id :: BundleId
       , template_data :: [(String, String)]
       , next :: String
       }
      deriving (Eq, Ord, Read, Show, Typeable, Data)

data FeedTarget = SingleFeed | MultiFeed
      deriving (Eq, Ord, Read, Show, Typeable, Data)

instance ToJson FeedData where
    toJson feedData =
        JDObject $ Map.fromList
                     [ ("method",  toJson (case target feedData of SingleFeed -> "feedStory" ; MultiFeed -> "multiFeedStory")) 
                     , ("content", JDObject $ Map.fromList
                                     [("feed", JDObject $ Map.fromList
                                                 [ ("template_id", toJson (template_id feedData))
                                                 , ("template_data", assocToJSON (template_data feedData))
                                                 ])
                                     , ("next", toJson (next feedData))
                                     ])
                     ]



-- NOTE: there is little reason to supply more than one story per size these days
data TemplateBundle 
    = TemplateBundle
      { oneLineTemplateBundle :: [String]
      , shortTemplateBundle   :: [(Maybe String, String)]
      , actionLinks           :: [ActionLink]
      }

data FeedRegisterTemplateBundle
    = FeedRegisterTemplateBundle TemplateBundle

instance FacebookMethod FeedRegisterTemplateBundle where
    type FacebookResponse FeedRegisterTemplateBundle = BundleId
    parseResponse _ str = 
        case fromJsonString (undefined :: Integer) str of
          Right bid -> Right (BundleId bid)
          (Left e) -> Left $ "Failed to parse BundleId JSON object: " ++ str
    toParams (FeedRegisterTemplateBundle templateBundle) =
        return $ catMaybes $
           [ Just ("method","Feed.registerTemplateBundle")
           , Just ("one_line_story_templates", toJsonString (oneLineTemplateBundle templateBundle))
           , Just ("short_story_templates", show $ JDArray (flip map (shortTemplateBundle templateBundle) $ 
                                                                 \(mTitle, body) ->
                                                                     JDObject $ Map.fromList $ catMaybes $
                                                                                [ fmap (\title -> ("template_title", toJson title)) mTitle
                                                                                , Just ("template_body", toJson body)
                                                                                ]))
           , if (null $ actionLinks templateBundle) 
              then Nothing 
              else Just ("action_links", show $ JDArray (map actionLinkToJSON $ actionLinks templateBundle))
           ]

-- * Notifications

-- ** Notifications.get

data NotificationsGet
    = NotificationsGet

-- ** Notifications.send

data NotificationsSend 
    = NotificationsSend 
      { to_ids           :: [User]
      , notification     :: String
      , notificationType :: Maybe NotificationType
      }

data NotificationType
    = UserToUser | AppToUser
      deriving (Eq, Ord, Read, Show, Data, Typeable)

instance FacebookMethod NotificationsSend where
    type FacebookResponse NotificationsSend = [User]
    parseResponse _ jstr = 
        case fromJsonString [] jstr of
          Left e -> error $ "Notifications.send: " ++ show e
          Right str -> parseUserIds str
    toParams (NotificationsSend to_ids notification mNotificationType) =
        do sessionKey <- fb_sig_session_key
           return $ catMaybes $ 
                      [ Just ("method", "Notifications.send")
                      , Just ("to_ids", show $ JDArray (map (toJson . fbUid) to_ids))
                      , Just ("notification", notification)
                      , fmap (\key -> ("session_key", key)) sessionKey -- session key is required for user_to_user
                      , fmap (\notificationType ->
                                  ("type", case notificationType of
                                             UserToUser -> "user_to_user"
                                             AppToUser  -> "app_to_user")) mNotificationType
                      ]

notificationsSend :: [User] -> String -> Maybe NotificationType -> Facebook (Either FacebookError [User])
notificationsSend users msg ntype = callMethodE (NotificationsSend users msg ntype)

-- ** Photos
-- <http://wiki.developers.facebook.com/index.php/Photos.get>
data PhotosGet
    = PhotosGet
      { subj_id :: Maybe User -- perhaps this should be a Bool?
      , album   :: Maybe AlbumId
      , photos  :: Maybe [PhotoId]
      }

instance RequiresSession PhotosGet

instance FacebookMethod PhotosGet where
    type FacebookResponse PhotosGet = String
    parseResponse _ jstr = Right jstr
{-
        case fromJsonString [] jstr of
          Left e -> error $ "Photos.get: " ++ show e
          Right str -> str
-}
    toParams (PhotosGet mSubjId mAlbum mPhotos) =
        do mSessionKey <- fb_sig_session_key
           return $ catMaybes $ 
                      [ Just ("method", "Photos.get")
                      , fmap (\key  -> ("session_key", key)) mSessionKey -- session key is required
                      , fmap (\subj -> ("subj_id", show (fbUid subj))) mSubjId
                      , fmap (\albumId  -> ("aid",  show (aid albumId))) mAlbum
                      , fmap (\pids -> ("pids", toCommaList $ map (show . pid) pids)) mPhotos
                      ]

photosGet :: Maybe User -> Maybe AlbumId -> Maybe [PhotoId] -> Facebook (Either FacebookError String)
photosGet mUser mAlbum mPhotos = callMethodE (PhotosGet mUser mAlbum mPhotos)

-- ** Get friends

--data FriendsGet = FriendsGet
--
--instance RequiresSession FriendsGet
--
--instance FacebookMethod FriendsGet where
--    type FacebookResponse FriendsGet = [User]
--    parseResponse _ jstr =
--        case fromJsonString [] jstr of
--          Left e -> error $ "Friends.get: " ++ show e
--          Right str -> parseUserIds str
--    toParams FriendsGet = do
--      mSessionKey <- fb_sig_session_key
--      return $ catMaybes $ 
--                 [ Just ("method", "Friends.get")
--                 --, fmap (\key  -> ("session_key", key)) mSessionKey -- session key is required
--                 ]

{-|
 - Gets a list of the current user's friends.
 -}
getFriends :: Facebook (Either String [User])
getFriends = do
  uid <- fb_user
  seskey <- fb_sig_session_key
  let u = show $ fbUid $ fromJust uid
      s = fromJust seskey
  res <- execRequest =<< buildRequest
    [("method", "friends.get")
    ,("session_key", s)
    ]
  return $ fromJsonString [] res

getAppUsers :: Facebook (Either String [User])
getAppUsers = do
  uid <- fb_user
  seskey <- fb_sig_session_key
  let u = show $ fbUid $ fromJust uid
      s = fromJust seskey
  res <- execRequest =<< buildRequest
    [("method", "friends.getAppUsers")
    ,("session_key", s)
    ]
  return $ fromJsonString [] res


