packages feed

twitter-conduit (empty) → 0.0.1

raw patch · 21 files changed

+2089/−0 lines, 21 filesdep +aesondep +attoparsecdep +attoparsec-conduitsetup-changed

Dependencies added: aeson, attoparsec, attoparsec-conduit, authenticate-oauth, base, bytestring, case-insensitive, conduit, containers, data-default, directory, doctest, failure, filepath, hlint, http-client-multipart, http-conduit, http-types, lens, lifted-base, monad-control, monad-logger, network, process, resourcet, scotty, shakespeare-text, template-haskell, text, time, transformers, transformers-base, twitter-conduit, twitter-types

Files

+ LICENSE view
@@ -0,0 +1,26 @@+Copyright (c)2011-2014, Takahiro Himura++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+import Distribution.Simple+main = defaultMain
+ Web/Twitter/Conduit.hs view
@@ -0,0 +1,12 @@+module Web.Twitter.Conduit+       ( module Export+       ) where++import Web.Twitter.Conduit.Base as Export+import Web.Twitter.Conduit.Api as Export+import Web.Twitter.Conduit.Status as Export+import Web.Twitter.Conduit.Monad as Export+import Web.Twitter.Conduit.Stream as Export+import Web.Twitter.Conduit.Request as Export+import Web.Twitter.Conduit.Parameters as Export+import Web.Twitter.Types.Lens as Export
+ Web/Twitter/Conduit/Api.hs view
@@ -0,0 +1,442 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 704+{-# LANGUAGE ConstraintKinds #-}+#endif++module Web.Twitter.Conduit.Api+       (+       -- * Search+         Search+       , search++       -- * Direct Messages+       , DirectMessages+       , directMessages+       , DirectMessagesSent+       , directMessagesSent+       , DirectMessagesShow+       , directMessagesShow+       , DirectMessagesDestroy+       , directMessagesDestroy+       , DirectMessagesNew+       , directMessagesNew++       -- * Friends & Followers+       -- , friendshipsNoRetweetsIds+       , FriendsIds+       , friendsIds+       , FollowersIds+       , followersIds+       -- , friendshipsLookup+       -- , friendshipsIncoming+       -- , friendshipsOutgoing+       , FriendshipsCreate+       , friendshipsCreate+       -- , friendshipsDestroy+       -- , friendshipsUpdate+       -- , friendshipsShow+       , FriendsList+       , friendsList+       -- , followersList++       -- * Users+       -- , accountSettings+       , AccountVerifyCredentials+       , accountVerifyCredentials+       -- , accountSettingsUpdate+       -- , accountUpdateDeliveryDevice+       -- , accountUpdateProfile+       -- , accountUpdateProfileBackgroundImage+       -- , accountUpdateProfileColors+       -- , accoutUpdateProfileImage+       -- , blocksList+       -- , blocksIds+       -- , blocksCreate+       -- , blocksDestroy++       , UsersLookup+       , usersLookup+       , UsersShow+       , usersShow+       -- , usersSearch+       -- , usersContributees+       -- , usersContributors+       -- , accuntRemoveProfileBanner+       -- , accuntUpdateProfileBanner+       -- , accuntProfileBanner++       -- * Suggested Users+       -- , usersSuggestionsSlug+       -- , usersSuggestions+       -- , usersSuggestionsSlugMembers++       -- * Favorites+       -- , favoritesList+       , FavoritesDestroy+       , favoritesDestroy+       , FavoritesCreate+       , favoritesCreate++       -- * Lists+       -- , listsStatuses+       -- , listsMemberships+       -- , listsSubscribers+       -- , listsSubscribersShow+       -- , listsMembersShow+       , ListsMembers+       , listsMembers+       -- , lists+       -- , listsShow+       -- , listsSubscriptions+       ) where++import Web.Twitter.Types+import Web.Twitter.Conduit.Parameters+import Web.Twitter.Conduit.Parameters.TH+import Web.Twitter.Conduit.Base+import Web.Twitter.Conduit.Request+import Web.Twitter.Conduit.Cursor++import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import Data.Default++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> import Control.Lens++data Search+-- | Returns search query.+--+-- You can perform a search query using 'call':+--+-- @+-- res <- 'call' ('search' \"search text\")+-- 'liftIO' . 'print' $ res ^. 'searchResultStatuses'+-- @+--+-- >>> search "search text"+-- APIRequestGet "https://api.twitter.com/1.1/search/tweets.json" [("q","search text")]+-- >>> search "search text" & lang ?~ "ja" & count ?~ 100+-- APIRequestGet "https://api.twitter.com/1.1/search/tweets.json" [("count","100"),("lang","ja"),("q","search text")]+search :: T.Text -- ^ search string+       -> APIRequest Search (SearchResult [SearchStatus])+search q = APIRequestGet (endpoint ++ "search/tweets.json") [("q", T.encodeUtf8 q)]+deriveHasParamInstances ''Search+    [ "lang"+    , "locale"+    -- , "result_type"+    , "count"+    , "until"+    , "since_id"+    , "max_id"+    , "include_entities"+    -- , "callback"  (needless)+    ]++data DirectMessages+-- | Returns query data which asks recent direct messages sent to the authenticating user.+--+-- You can perform a query using 'call':+--+-- @+-- res <- 'call' '$' 'directMessages' '&' 'count' '?~' 100+-- @+--+-- >>> directMessages+-- APIRequestGet "https://api.twitter.com/1.1/direct_messages.json" []+-- >>> directMessages & count ?~ 100+-- APIRequestGet "https://api.twitter.com/1.1/direct_messages.json" [("count","100")]+directMessages :: APIRequest DirectMessages [DirectMessage]+directMessages = APIRequestGet (endpoint ++ "direct_messages.json") def+deriveHasParamInstances ''DirectMessages+    [ "since_id"+    , "max_id"+    , "count"+    , "include_entities"+    , "skip_status"+    ]++data DirectMessagesSent+-- | Returns query data which asks recent direct messages sent by the authenticating user.+--+-- You can perform a query using 'call':+--+-- @+-- res <- 'call' '$' 'directMessagesSent' '&' 'count' '?~' 100+-- @+--+-- >>> directMessagesSent+-- APIRequestGet "https://api.twitter.com/1.1/direct_messages/sent.json" []+-- >>> directMessagesSent & count ?~ 100+-- APIRequestGet "https://api.twitter.com/1.1/direct_messages/sent.json" [("count","100")]+directMessagesSent :: APIRequest DirectMessagesSent [DirectMessage]+directMessagesSent = APIRequestGet (endpoint ++ "direct_messages/sent.json") def+deriveHasParamInstances ''DirectMessagesSent+    [ "since_id"+    , "max_id"+    , "count"+    , "include_entities"+    , "page"+    , "skip_status"+    ]++data DirectMessagesShow+-- | Returns query data which asks a single direct message, specified by an id parameter.+--+-- You can perform a query using 'call':+--+-- @+-- res <- 'call' '$' 'directMessagesShow' 1234567890+-- @+--+-- >>> directMessagesShow 1234567890+-- APIRequestGet "https://api.twitter.com/1.1/direct_messages/show.json" [("id","1234567890")]+directMessagesShow :: StatusId -> APIRequest DirectMessagesShow DirectMessage+directMessagesShow sId = APIRequestGet (endpoint ++ "direct_messages/show.json") [("id", showBS sId)]++data DirectMessagesDestroy+-- | Returns post data which destroys the direct message specified in the required ID parameter.+--+-- You can perform a query using 'call':+--+-- @+-- res <- 'call' '$' 'directMessagesDestroy' 1234567890+-- @+--+-- >>> directMessagesDestroy 1234567890+-- APIRequestPost "https://api.twitter.com/1.1/direct_messages/destroy.json" [("id","1234567890")]+directMessagesDestroy :: StatusId -> APIRequest DirectMessagesDestroy DirectMessage+directMessagesDestroy sId = APIRequestPost (endpoint ++ "direct_messages/destroy.json") [("id", showBS sId)]+deriveHasParamInstances ''DirectMessagesDestroy+    [ "include_entities"+    ]++data DirectMessagesNew+-- | Returns post data which sends a new direct message to the specified user from the authenticating user.+--+-- You can perform a post using 'call':+--+-- @+-- res <- 'call' '$' 'directMessagesNew' (ScreenNameParam \"thimura\") \"Hello DM\"+-- @+--+-- >>> directMessagesNew (ScreenNameParam "thimura") "Hello DM"+-- APIRequestPost "https://api.twitter.com/1.1/direct_messages/new.json" [("text","Hello DM"),("screen_name","thimura")]+-- >>> directMessagesNew (UserIdParam 69179963) "Hello thimura! by UserId"+-- APIRequestPost "https://api.twitter.com/1.1/direct_messages/new.json" [("text","Hello thimura! by UserId"),("user_id","69179963")]+directMessagesNew :: UserParam -> T.Text -> APIRequest DirectMessagesNew DirectMessage+directMessagesNew q msg = APIRequestPost (endpoint ++ "direct_messages/new.json") (("text", T.encodeUtf8 msg):mkUserParam q)++data FriendsIds+-- | Returns query data which asks a collection of user IDs for every user the specified user is following.+--+-- You can perform a query using 'call':+--+-- @+-- res <- 'call' '$' 'friendsIds' ('ScreenNameParam' \"thimura\")+-- @+--+-- Or, you can iterate with 'sourceWithCursor':+--+-- @+-- 'sourceWithCursor' ('friendsIds' ('ScreenNameParam' \"thimura\")) $$ CL.consume+-- @+--+-- >>> friendsIds (ScreenNameParam "thimura")+-- APIRequestGet "https://api.twitter.com/1.1/friends/ids.json" [("screen_name","thimura")]+-- >>> friendsIds (ScreenNameParam "thimura") & count ?~ 5000+-- APIRequestGet "https://api.twitter.com/1.1/friends/ids.json" [("count","5000"),("screen_name","thimura")]+friendsIds :: UserParam -> APIRequest FriendsIds (WithCursor IdsCursorKey UserId)+friendsIds q = APIRequestGet (endpoint ++ "friends/ids.json") (mkUserParam q)+deriveHasParamInstances ''FriendsIds+    [ "cursor"+    -- , "stringify_ids" -- (needless)+    , "count"+    ]++data FollowersIds+-- | Returns query data which asks a collection of user IDs for every user following the specified user.+--+-- You can perform a query using 'call':+--+-- @+-- res <- 'call' '$' 'followersIds' ('ScreenNameParam' \"thimura\")+-- @+--+-- Or, you can iterate with 'sourceWithCursor':+--+-- @+-- 'sourceWithCursor' ('followersIds' ('ScreenNameParam' \"thimura\")) $$ CL.consume+-- @+--+-- >>> followersIds (ScreenNameParam "thimura")+-- APIRequestGet "https://api.twitter.com/1.1/followers/ids.json" [("screen_name","thimura")]+-- >>> followersIds (ScreenNameParam "thimura") & count ?~ 5000+-- APIRequestGet "https://api.twitter.com/1.1/followers/ids.json" [("count","5000"),("screen_name","thimura")]+followersIds :: UserParam -> APIRequest FollowersIds (WithCursor IdsCursorKey UserId)+followersIds q = APIRequestGet (endpoint ++ "followers/ids.json") (mkUserParam q)+deriveHasParamInstances ''FollowersIds+    [ "cursor"+    -- , "stringify_ids" -- (needless)+    , "count"+    ]++data FriendshipsCreate+-- | Returns post data which follows the user specified in the ID parameter.+--+-- You can perform request by using 'call':+--+-- @+-- res <- 'call' '$' 'friendshipsCreate' ('ScreenNameParam' \"thimura\")+-- @+--+-- >>> friendshipsCreate (ScreenNameParam "thimura")+-- APIRequestPost "https://api.twitter.com/1.1/friendships/create.json" [("screen_name","thimura")]+-- >>> friendshipsCreate (UserIdParam 69179963)+-- APIRequestPost "https://api.twitter.com/1.1/friendships/create.json" [("user_id","69179963")]+friendshipsCreate :: UserParam -> APIRequest FriendshipsCreate User+friendshipsCreate user = APIRequestPost (endpoint ++ "friendships/create.json") (mkUserParam user)+deriveHasParamInstances ''FriendshipsCreate+    [ "follow"+    ]++data FriendsList+-- | Returns query data which asks a cursored collection of user objects for every user the specified users is following.+--+-- You can perform request by using 'call':+--+-- @+-- res <- 'call' '$' 'friendsList' ('ScreenNameParam' \"thimura\")+-- @+--+-- Or, you can iterate with 'sourceWithCursor':+--+-- @+-- 'sourceWithCursor' ('friendsList' ('ScreenNameParam' \"thimura\")) $$ CL.consume+-- @+--+-- >>> friendsList (ScreenNameParam "thimura")+-- APIRequestGet "https://api.twitter.com/1.1/friends/list.json" [("screen_name","thimura")]+-- >>> friendsList (UserIdParam 69179963)+-- APIRequestGet "https://api.twitter.com/1.1/friends/list.json" [("user_id","69179963")]+friendsList :: UserParam -> APIRequest FriendsList (WithCursor UsersCursorKey User)+friendsList q = APIRequestGet (endpoint ++ "friends/list.json") (mkUserParam q)+deriveHasParamInstances ''FriendsList+    [ "cursor"+    , "count"+    , "skip_status"+    , "include_user_entities"+    ]++data AccountVerifyCredentials+-- | Returns query data asks that the credential is valid.+--+-- You can perform request by using 'call':+--+-- @+-- res <- 'call' '$' 'accountVerifyCredentials'+-- @+--+-- >>> accountVerifyCredentials+-- APIRequestGet "https://api.twitter.com/1.1/account/verify_credentials.json" []+accountVerifyCredentials :: APIRequest AccountVerifyCredentials User+accountVerifyCredentials = APIRequestGet (endpoint ++ "account/verify_credentials.json") []+deriveHasParamInstances ''AccountVerifyCredentials+    [ "include_entities"+    , "skip_status"+    ]++data UsersLookup+-- | Returns query data asks user objects.+--+-- You can perform request by using 'call':+--+-- @+-- res <- 'call' '$' 'usersLookup' ('ScreenNameListParam' [\"thimura\", \"twitterapi\"])+-- @+--+-- >>> usersLookup (ScreenNameListParam ["thimura", "twitterapi"])+-- APIRequestGet "https://api.twitter.com/1.1/users/lookup.json" [("screen_name","thimura,twitterapi")]+usersLookup :: UserListParam -> APIRequest UsersLookup [User]+usersLookup q = APIRequestGet (endpoint ++ "users/lookup.json") (mkUserListParam q)+deriveHasParamInstances ''UsersLookup+    [ "include_entities"+    ]++data UsersShow+-- | Returns query data asks the user specified by user id or screen name parameter.+--+-- You can perform request by using 'call':+--+-- @+-- res <- 'call' '$' 'usersShow' ('ScreenNameParam' \"thimura\")+-- @+--+-- >>> usersShow (ScreenNameParam "thimura")+-- APIRequestGet "https://api.twitter.com/1.1/users/show.json" [("screen_name","thimura")]+usersShow :: UserParam -> APIRequest UsersShow User+usersShow q = APIRequestGet (endpoint ++ "users/show.json") (mkUserParam q)+deriveHasParamInstances ''UsersShow+    [ "include_entities"+    ]++data FavoritesCreate+-- | Returns post data which favorites the status specified in the ID parameter as the authenticating user.+--+-- You can perform request by using 'call':+--+-- @+-- res <- 'call' '$' 'favoritesCreate' 1234567890+-- @+--+-- >>> favoritesCreate 1234567890+-- APIRequestPost "https://api.twitter.com/1.1/favorites/create.json" [("id","1234567890")]+favoritesCreate :: StatusId -> APIRequest FavoritesCreate Status+favoritesCreate sid = APIRequestPost (endpoint ++ "favorites/create.json") [("id", showBS sid)]+deriveHasParamInstances ''FavoritesCreate+    [ "include_entities"+    ]++data FavoritesDestroy+-- | Returns post data unfavorites the status specified in the ID paramter as the authenticating user.+--+-- You can perform request by using 'call':+--+-- @+-- res <- 'call' '$' 'favoritesDestroy' 1234567890+-- @+--+-- >>> favoritesDestroy 1234567890+-- APIRequestPost "https://api.twitter.com/1.1/favorites/destroy.json" [("id","1234567890")]+favoritesDestroy :: StatusId -> APIRequest FavoritesDestroy Status+favoritesDestroy sid = APIRequestPost (endpoint ++ "favorites/destroy.json") [("id", showBS sid)]+deriveHasParamInstances ''FavoritesDestroy+    [ "include_entities"+    ]++data ListsMembers+-- | Returns query data asks the members of the specified list.+--+-- You can perform request by using 'call':+--+-- @+-- res <- 'call' '$' 'listsMembers' ('ListNameParam' "thimura/haskell")+-- @+--+-- >>> listsMembers (ListNameParam "thimura/haskell")+-- APIRequestGet "https://api.twitter.com/1.1/lists/members.json" [("slug","haskell"),("owner_screen_name","thimura")]+-- >>> listsMembers (ListIdParam 20849097)+-- APIRequestGet "https://api.twitter.com/1.1/lists/members.json" [("list_id","20849097")]+listsMembers :: ListParam -> APIRequest ListsMembers (WithCursor UsersCursorKey User)+listsMembers q = APIRequestGet (endpoint ++ "lists/members.json") (mkListParam q)+deriveHasParamInstances ''ListsMembers+    [ "cursor"+    , "skip_status"+    ]+
+ Web/Twitter/Conduit/Base.hs view
@@ -0,0 +1,211 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE QuasiQuotes #-}+#if __GLASGOW_HASKELL__ >= 704+{-# LANGUAGE ConstraintKinds #-}+#else+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-}+#endif++module Web.Twitter.Conduit.Base+       ( api+       , apiRequest+       , call+       , sourceWithMaxId+       , sourceWithCursor+       , TwitterBaseM+       , endpoint+       , makeRequest+       , sinkJSON+       , sinkFromJSON+       , showBS+       ) where++import Web.Twitter.Conduit.Monad+import Web.Twitter.Conduit.Error+import Web.Twitter.Conduit.Parameters+import Web.Twitter.Conduit.Request+import Web.Twitter.Conduit.Cursor+import Web.Twitter.Types.Lens++import Network.HTTP.Conduit+import Network.HTTP.Client.MultipartFormData+import qualified Network.HTTP.Types as HT+import qualified Data.Conduit as C+import qualified Data.Conduit.List as CL++import Data.Aeson+import Data.Aeson.Lens+import qualified Data.Conduit.Attoparsec as CA+import qualified Data.Text.Encoding as T+import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as S8+import Control.Monad.IO.Class+import Control.Monad.Trans.Class (lift)+import Text.Shakespeare.Text+import Control.Monad.Logger+import Control.Lens+import Unsafe.Coerce++#if __GLASGOW_HASKELL__ >= 704+type TwitterBaseM m = ( C.MonadResource m+                      , MonadLogger m+                      )+#else+class (C.MonadResource m, MonadLogger m) => TwitterBaseM m+instance (C.MonadResource m, MonadLoger m) => TwitterBaseM m+#endif++makeRequest :: MonadIO m+            => HT.Method -- ^ HTTP request method (GET or POST)+            -> String -- ^ API Resource URL+            -> HT.SimpleQuery -- ^ Query+            -> TW m Request+makeRequest m url query = do+    p <- getProxy+    req <- liftIO $ parseUrl url+    return $ req { method = m+                 , queryString = HT.renderSimpleQuery False query+                 , proxy = p }++api :: TwitterBaseM m+    => HT.Method -- ^ HTTP request method (GET or POST)+    -> String -- ^ API Resource URL+    -> HT.SimpleQuery -- ^ Query+    -> TW m (C.ResumableSource (TW m) ByteString)+api m url query =+    apiRequest =<< makeRequest m url query++apiRequest :: TwitterBaseM m+           => Request+           -> TW m (C.ResumableSource (TW m) ByteString)+apiRequest req = do+    signedReq <- signOAuthTW req+    $(logDebug) [st|Signed Request: #{show signedReq}|]+    mgr <- getManager+    res <- http signedReq mgr+    $(logDebug) [st|Response Status: #{show $ responseStatus res}|]+    $(logDebug) [st|Response Header: #{show $ responseHeaders res}|]+    return $ responseBody res++endpoint :: String+endpoint = "https://api.twitter.com/1.1/"++apiValue :: (TwitterBaseM m, FromJSON a)+         => HT.Method -- ^ HTTP request method (GET or POST)+         -> String -- ^ API Resource URL+         -> HT.SimpleQuery -- ^ Query+         -> TW m a+apiValue m url query = do+    src <- api m url query+    src C.$$+- sinkFromJSON++call :: (TwitterBaseM m, FromJSON responseType)+     => APIRequest apiName responseType+     -> TW m responseType+call = call'++call' :: (TwitterBaseM m, FromJSON value)+      => APIRequest apiName responseType+      -> TW m value+call' (APIRequestGet u pa) = apiValue "GET" u pa+call' (APIRequestPost u pa) = apiValue "POST" u pa+call' (APIRequestPostMultipart u param prt) = do+    req <- formDataBody body =<< makeRequest "POST" u []+    src <- apiRequest req+    src C.$$+- sinkFromJSON+  where+    body = prt ++ partParam+    partParam = map (uncurry partBS . over _1 T.decodeUtf8) param++sourceWithMaxId :: ( TwitterBaseM m+                   , FromJSON responseType+                   , AsStatus responseType+                   , HasMaxIdParam (APIRequest apiName [responseType])+                   )+                => APIRequest apiName [responseType]+                -> C.Source (TW m) responseType+sourceWithMaxId = loop+  where+    loop req = do+        res <- lift $ call req+        case getMinId res of+            Just mid -> do+                CL.sourceList res+                loop $ req & maxId ?~ mid - 1+            Nothing -> CL.sourceList res+    getMinId = minimumOf (traverse . status_id)++sourceWithMaxId' :: ( TwitterBaseM m+                    , HasMaxIdParam (APIRequest apiName [responseType])+                    )+                 => APIRequest apiName [responseType]+                 -> C.Source (TW m) Value+sourceWithMaxId' = loop+  where+    loop req = do+        res <- lift $ call' req+        case getMinId res of+            Just mid -> do+                CL.sourceList res+                loop $ req & maxId ?~ mid - 1+            Nothing -> CL.sourceList res+    getMinId = minimumOf (traverse . key "id" . _Integer)++sourceWithCursor :: ( TwitterBaseM m+                    , FromJSON responseType+                    , CursorKey ck+                    , HasCursorParam (APIRequest apiName (WithCursor ck responseType))+                    )+                 => APIRequest apiName (WithCursor ck responseType)+                 -> C.Source (TW m) responseType+sourceWithCursor req = loop (-1)+  where+    loop 0 = CL.sourceNull+    loop cur = do+        res <- lift $ call $ req & cursor ?~ cur+        CL.sourceList $ contents res+        loop $ nextCursor res++sourceWithCursor' :: ( TwitterBaseM m+                     , FromJSON responseType+                     , CursorKey ck+                     , HasCursorParam (APIRequest apiName (WithCursor ck responseType))+                     )+                  => APIRequest apiName (WithCursor ck responseType)+                  -> C.Source (TW m) Value+sourceWithCursor' req = loop (-1)+  where+    relax :: FromJSON value+          => APIRequest apiName (WithCursor ck responseType)+          -> APIRequest apiName (WithCursor ck value)+    relax = unsafeCoerce+    loop 0 = CL.sourceNull+    loop cur = do+        res <- lift $ call $ relax $ req & cursor ?~ cur+        CL.sourceList $ contents res+        loop $ nextCursor res++sinkJSON :: ( C.MonadThrow m+            , MonadLogger m+            ) => C.Consumer ByteString m Value+sinkJSON = do+    js <- CA.sinkParser json+    $(logDebug) [st|Response JSON: #{show js}|]+    return js++sinkFromJSON :: ( FromJSON a+                , C.MonadThrow m+                , MonadLogger m+                ) => C.Consumer ByteString m a+sinkFromJSON = do+    v <- sinkJSON+    case fromJSON v of+        Error err -> lift $ C.monadThrow $ TwitterError err+        Success r -> return r++showBS :: Show a => a -> ByteString+showBS = S8.pack . show
+ Web/Twitter/Conduit/Cursor.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Web.Twitter.Conduit.Cursor+       ( CursorKey (..)+       , IdsCursorKey+       , UsersCursorKey+       , WithCursor (..)+       ) where++import Web.Twitter.Types (checkError)+import qualified Data.Text as T+import Data.Aeson+import Data.Monoid+import Control.Applicative++-- $setup+-- >>> type UserId = Integer++class CursorKey a where+    cursorKey :: a -> T.Text++-- | Phantom type to specify the key which point out the content in the response.+data IdsCursorKey+instance CursorKey IdsCursorKey where+    cursorKey = const "ids"++-- | Phantom type to specify the key which point out the content in the response.+data UsersCursorKey+instance CursorKey UsersCursorKey where+    cursorKey = const "users"++-- | A wrapper for API responses which have "next_cursor" field.+--+-- The first type parameter of 'WithCursor' specifies the field name of contents.+--+-- >>> let Just res = decode "{\"previous_cursor\": 0, \"next_cursor\": 1234567890, \"ids\": [1111111111]}" :: Maybe (WithCursor IdsCursorKey UserId)+-- >>> nextCursor res+-- 1234567890+-- >>> contents res+-- [1111111111]+--+-- >>> let Just res = decode "{\"previous_cursor\": 0, \"next_cursor\": 0, \"users\": [1000]}" :: Maybe (WithCursor UsersCursorKey UserId)+-- >>> nextCursor res+-- 0+-- >>> contents res+-- [1000]+data WithCursor cursorKey wrapped = WithCursor+    { previousCursor :: Integer+    , nextCursor :: Integer+    , contents :: [wrapped]+    }++instance (FromJSON wrapped, CursorKey c) =>+         FromJSON (WithCursor c wrapped) where+    parseJSON (Object o) = checkError o >>+      WithCursor <$> o .:  "previous_cursor"+                 <*> o .:  "next_cursor"+                 <*> o .:  cursorKey (undefined :: c)+    parseJSON _ = mempty
+ Web/Twitter/Conduit/Error.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Web.Twitter.Conduit.Error+       ( TwitterError (..)+       ) where++import Control.Exception+import Data.Data++data TwitterError+  = TwitterError String+  deriving (Show, Data, Typeable)++instance Exception TwitterError+
+ Web/Twitter/Conduit/Monad.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE CPP #-}++module Web.Twitter.Conduit.Monad+       ( TW+       , TWToken (..)+       , TWInfo (..)+       , setCredential+       , runTW+       , runTWManager+       , getProxy+       , getManager+       , signOAuthTW+       , twitterOAuth+       )+       where++import Web.Authenticate.OAuth+import Network.HTTP.Conduit+import Data.Default+import Control.Monad.IO.Class+import Control.Monad.Trans.Reader+import Control.Monad.Trans.Resource++-- $setup+-- >>> :set -XOverloadedStrings++type TW m = ReaderT TWEnv m++data TWToken = TWToken+    { twOAuth :: OAuth+    , twCredential :: Credential+    }+instance Default TWToken where+    def = TWToken twitterOAuth (Credential [])++data TWInfo = TWInfo+    { twToken :: TWToken+    , twProxy :: Maybe Proxy+    }+instance Default TWInfo where+    def = TWInfo+        { twToken = def+        , twProxy = Nothing+        }++twitterOAuth :: OAuth+twitterOAuth =+    def { oauthServerName = "twitter"+        , oauthRequestUri = "https://api.twitter.com/oauth/request_token"+        , oauthAccessTokenUri = "https://api.twitter.com/oauth/access_token"+        , oauthAuthorizeUri = "https://api.twitter.com/oauth/authorize"+        , oauthConsumerKey = error "You MUST specify oauthConsumerKey parameter."+        , oauthConsumerSecret = error "You MUST specify oauthConsumerSecret parameter."+        , oauthSignatureMethod = HMACSHA1+        , oauthCallback = Nothing+        }++data TWEnv = TWEnv+    { twInfo :: TWInfo+    , twManager :: Manager+    }++-- | set OAuth keys and Credentials to TWInfo.+--+-- >>> let proxy = Proxy "localhost" 8080+-- >>> let twinfo = def { twProxy = Just proxy }+-- >>> let oauth = twitterOAuth { oauthConsumerKey = "consumer_key", oauthConsumerSecret = "consumer_secret" }+-- >>> let credential = Credential [("oauth_token","...")]+-- >>> let twinfo2 = setCredential oauth credential twinfo+-- >>> oauthConsumerKey . twOAuth . twToken $ twinfo2+-- "consumer_key"+-- >>> twProxy twinfo2 == Just proxy+-- True+setCredential :: OAuth -> Credential -> TWInfo -> TWInfo+setCredential oa cred env+  = TWInfo+    { twToken = TWToken oa cred+    , twProxy = twProxy env+    }++-- | create a new http-conduit manager and run TW monad.+--+-- >>> runTW def getProxy+-- Nothing+-- >>> runTW def $ asks (twCredential . twToken . twInfo)+-- Credential {unCredential = []}+runTW :: ( MonadBaseControl IO m+         , MonadIO m+         ) => TWInfo -> TW (ResourceT m) a -> m a+runTW info st = withManager $ \mgr -> runTWManager info mgr st++runTWManager :: MonadBaseControl IO m => TWInfo -> Manager -> TW (ResourceT m) a -> ResourceT m a+runTWManager info mgr st = runReaderT st $ TWEnv info mgr++getProxy ::Monad m => TW m (Maybe Proxy)+getProxy = asks (twProxy . twInfo)++getManager :: Monad m => TW m Manager+getManager = asks twManager++signOAuthTW :: MonadUnsafeIO m+            => Request+            -> TW m Request+signOAuthTW req = do+    TWToken oa cred <- asks (twToken . twInfo)+    signOAuth oa cred req
+ Web/Twitter/Conduit/Parameters.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}++module Web.Twitter.Conduit.Parameters+       ( Parameters (..)+       , HasSinceIdParam (..)+       , HasCountParam (..)+       , HasMaxIdParam (..)+       , HasPageParam (..)+       , HasCursorParam (..)+       , HasTrimUserParam (..)+       , HasExcludeRepliesParam (..)+       , HasContributorDetailsParam (..)+       , HasIncludeEntitiesParam (..)+       , HasIncludeUserEntitiesParam (..)+       , HasIncludeRtsParam (..)+       , HasIncludeMyRetweetParam (..)+       , HasInReplyToStatusIdParam (..)+       , HasDisplayCoordinatesParam (..)+       , HasPossiblySensitiveParam (..)+       , HasLangParam (..)+       , HasLocaleParam (..)+       , HasUntilParam (..)+       , HasSkipStatusParam (..)+       , HasFollowParam (..)++       , UserParam(..)+       , UserListParam(..)+       , ListParam(..)+       , mkUserParam+       , mkUserListParam+       , mkListParam+       ) where++import Web.Twitter.Conduit.Parameters.Internal+import Web.Twitter.Conduit.Parameters.TH+import Web.Twitter.Types+import Data.Time.Calendar (Day)+import Data.Text.Strict.Lens+import Data.Text (Text)+import Control.Lens++import qualified Network.HTTP.Types as HT+import qualified Data.ByteString.Char8 as S8++data UserParam = UserIdParam UserId | ScreenNameParam String+               deriving (Show, Eq)+data UserListParam = UserIdListParam [UserId] | ScreenNameListParam [String]+                   deriving (Show, Eq)+data ListParam = ListIdParam Integer | ListNameParam String+               deriving (Show, Eq)++defineHasParamClass "count" ''Integer 'readShow+defineHasParamClass "since_id" ''Integer 'readShow+defineHasParamClass "max_id" ''Integer 'readShow+defineHasParamClass "page" ''Integer 'readShow+defineHasParamClass "cursor" ''Integer 'readShow+defineHasParamClass "trim_user" ''Bool 'booleanQuery+defineHasParamClass "exclude_replies" ''Bool 'booleanQuery+defineHasParamClass "contributor_details" ''Bool 'booleanQuery+defineHasParamClass "include_entities" ''Bool 'booleanQuery+defineHasParamClass "include_user_entities" ''Bool 'booleanQuery+defineHasParamClass "include_rts" ''Bool 'booleanQuery+defineHasParamClass "include_my_retweet" ''Bool 'booleanQuery+defineHasParamClass "in_reply_to_status_id" ''StatusId 'readShow+defineHasParamClass "display_coordinates" ''Bool 'booleanQuery+defineHasParamClass "possibly_sensitive" ''Bool 'booleanQuery+defineHasParamClass "lang" ''Text 'utf8+defineHasParamClass "locale" ''Text 'utf8+defineHasParamClass "until" ''Day 'readShow+defineHasParamClass "skip_status" ''Bool 'booleanQuery+defineHasParamClass "follow" ''Bool 'booleanQuery++-- | converts 'UserParam' to 'HT.SimpleQuery'.+--+-- >>> mkUserParam $ UserIdParam 123456+-- [("user_id","123456")]+-- >>> mkUserParam $ ScreenNameParam "thimura"+-- [("screen_name","thimura")]+mkUserParam :: UserParam -> HT.SimpleQuery+mkUserParam (UserIdParam uid) =  [("user_id", readShow # uid)]+mkUserParam (ScreenNameParam sn) = [("screen_name", S8.pack sn)]++-- | converts 'UserListParam' to 'HT.SimpleQuery'.+--+-- >>> mkUserListParam $ UserIdListParam [123456]+-- [("user_id","123456")]+-- >>> mkUserListParam $ UserIdListParam [123456, 654321]+-- [("user_id","123456,654321")]+-- >>> mkUserListParam $ ScreenNameListParam ["thimura", "NikaidouShinku"]+-- [("screen_name","thimura,NikaidouShinku")]+mkUserListParam :: UserListParam -> HT.SimpleQuery+mkUserListParam (UserIdListParam uids) =  [("user_id", uids ^.. traversed . re readShow & S8.intercalate ",")]+mkUserListParam (ScreenNameListParam sns) = [("screen_name", S8.intercalate "," . map S8.pack $ sns)]++-- | converts 'ListParam' to 'HT.SimpleQuery'.+--+-- >>> mkListParam $ ListIdParam 123123+-- [("list_id","123123")]+-- >>> mkListParam $ ListNameParam "thimura/haskell"+-- [("slug","haskell"),("owner_screen_name","thimura")]+mkListParam :: ListParam -> HT.SimpleQuery+mkListParam (ListIdParam lid) =  [("list_id", readShow # lid)]+mkListParam (ListNameParam listname) =+    [("slug", S8.pack lstName),+     ("owner_screen_name", S8.pack screenName)]+  where+    (screenName, ln) = span (/= '/') listname+    lstName = drop 1 ln+
+ Web/Twitter/Conduit/Parameters/Internal.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Rank2Types #-}++module Web.Twitter.Conduit.Parameters.Internal+       ( Parameters(..)+       , readShow+       , booleanQuery+       , wrappedParam+       ) where++import qualified Network.HTTP.Types as HT+import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8+import Control.Lens++class Parameters a where+    params :: Lens' a HT.SimpleQuery++-- | This 'Prism' convert from a 'ByteString' to some value based on 'Read' and 'Show'+--+-- >>> readShow # 2+-- "2"+-- >>> "1024" ^? readShow :: Maybe Integer+-- Just 1024+readShow :: (Read a, Show a) => Prism' S.ByteString a+readShow = prism' (S8.pack . show) (readMaybe . S8.unpack)+  where+    readMaybe str = case [x | (x, t) <- reads str, ("", "") <- lex t] of+        [x] -> Just x+        _ -> Nothing++-- | This 'Prism' convert from a 'ByteString' to 'Bool' value.+--+-- >>> booleanQuery # True+-- "true"+-- >>> booleanQuery # False+-- "false"+-- >>> "true" ^? booleanQuery+-- Just True+-- >>> "1" ^? booleanQuery+-- Just True+-- >>> "t" ^? booleanQuery+-- Just True+-- >>> "test" ^? booleanQuery+-- Just False+booleanQuery :: Prism' S.ByteString Bool+booleanQuery = prism' bs sb+  where+    bs True = "true"+    bs False = "false"+    sb "true" = Just True+    sb "1" = Just True+    sb "t" = Just True+    sb _ = Just False++wrappedParam :: Parameters p => S.ByteString -> Prism' S.ByteString a -> Lens' p (Maybe a)+wrappedParam key aSBS = lens getter setter+   where+     getter = preview $ params . to (lookup key) . _Just . aSBS+     setter = flip (over params . replace key)+     replace k (Just v) = ((k, aSBS # v):) . dropAssoc k+     replace k Nothing = dropAssoc k+     dropAssoc k = filter ((/= k) . fst)
+ Web/Twitter/Conduit/Parameters/TH.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE TemplateHaskell #-}++module Web.Twitter.Conduit.Parameters.TH where++import Web.Twitter.Conduit.Parameters.Internal+import Language.Haskell.TH+import Control.Lens+import Data.Char++snakeToLowerCamel :: String -> String+snakeToLowerCamel [] = []+snakeToLowerCamel ('_':[]) = []+snakeToLowerCamel ('_':x:xs) = toUpper x : snakeToLowerCamel xs+snakeToLowerCamel str = f ++ snakeToLowerCamel next+  where (f, next) = span (/= '_') str++snakeToUpperCamel :: String -> String+snakeToUpperCamel = upcase . snakeToLowerCamel+  where+    upcase [] = []+    upcase (x:xs) = toUpper x : xs++paramNameToClassName :: String -> String+paramNameToClassName paramName = "Has" ++ snakeToUpperCamel paramName ++ "Param"++defineHasParamClass :: String -- ^ parameter name+                    -> Name -- ^ parameter type+                    -> Name -- ^ a Prism+                    -> Q [Dec]+defineHasParamClass paramName typeN prismN =+    defineHasParamClass' cNameS fNameS paramName typeN prismN+  where+    cNameS = paramNameToClassName paramName+    fNameS = snakeToLowerCamel paramName++defineHasParamClass' :: String -> String -> String -> Name -> Name -> Q [Dec]+defineHasParamClass' cNameS fNameS paramName typeN prismN = do+    a <- newName "a"+    cName <- newName cNameS+    fName <- newName fNameS+    let cCxt = cxt [classP ''Parameters [varT a]]+        tySig = sigD fName (appT (appT (conT ''Lens') (varT a)) (appT (conT ''Maybe) (conT typeN)))+        valDef = valD (varP fName) (normalB (appE (appE (varE 'wrappedParam) (litE (stringL paramName))) (varE prismN))) []+    dec <- classD cCxt cName [PlainTV a] [] [tySig, valDef]+    return [dec]++deriveHasParamInstances :: Name -- ^ target data type name+                        -> [String] -- ^ parameter name+                        -> Q [Dec]+deriveHasParamInstances typName paramNameList =+    mapM mkInstance cNameStrList+  where+    cNameStrList = map paramNameToClassName paramNameList+    mkInstance cn = instanceD (return []) (appT (conT (mkName cn)) targetType) []+    targetType = do+        a <- newName "a"+        appT (appT (conT (mkName "APIRequest")) (conT typName)) (varT a)
+ Web/Twitter/Conduit/Request.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE FlexibleInstances #-}++module Web.Twitter.Conduit.Request+       ( APIRequest(..)+       ) where++import Web.Twitter.Conduit.Parameters++import Network.HTTP.Client.MultipartFormData+import qualified Network.HTTP.Types as HT+import Control.Applicative++-- $setup+-- >>> :set -XOverloadedStrings -XRank2Types -XEmptyDataDecls -XFlexibleInstances+-- >>> import Control.Lens+-- >>> import Data.Default+-- >>> data SampleApi+-- >>> type SampleId = Integer+-- >>> instance HasCountParam (APIRequest SampleApi [SampleId])+-- >>> instance HasMaxIdParam (APIRequest SampleApi [SampleId])+-- >>> let sampleApiRequest :: APIRequest SampleApi [SampleId]; sampleApiRequest = APIRequestGet "https://api.twitter.com/sample/api.json" def++-- | API request. You should use specific builder functions instead of building this directly.+--+-- For example, if there were a @SampleApi@ type and a builder function which named @sampleApiRequest@.+-- In addition, @'APIRequest' SampleApi [SampleId]@ is a instance of both of 'HasCountParam' and 'HasMaxIdParam'.+--+-- @+-- data 'SampleApi'+-- type 'SampleId' = 'Integer'+-- instance 'HasCountParam' ('APIRequest' 'SampleApi' ['SampleId'])+-- instance 'HasMaxIdParam' ('APIRequest' 'SampleApi' ['SampleId'])+-- 'sampleApiRequest' :: 'APIRequest' 'SampleApi' ['SampleId']+-- 'sampleApiRequest' = 'APIRequestGet' \"https:\/\/api.twitter.com\/sample\/api.json\" 'def'+-- @+--+-- We can obtain request params from @'APIRequest' SampleApi [SampleId]@ :+--+-- >>> sampleApiRequest ^. params+-- []+--+-- And update request parameters.+--+-- >>> (sampleApiRequest & count ?~ 100 & maxId ?~ 1234567890) ^. params+-- [("max_id","1234567890"),("count","100")]+-- >>> (sampleApiRequest & count ?~ 100 & maxId ?~ 1234567890 & count .~ Nothing) ^. params+-- [("max_id","1234567890")]+data APIRequest apiName responseType+    = APIRequestGet+      { _url :: String+      , _params :: HT.SimpleQuery+      }+    | APIRequestPost+      { _url :: String+      , _params :: HT.SimpleQuery+      }+    | APIRequestPostMultipart+      { _url :: String+      , _params :: HT.SimpleQuery+      , _part :: [Part]+      }+instance Parameters (APIRequest apiName responseType) where+    params f (APIRequestGet u pa) = APIRequestGet u <$> f pa+    params f (APIRequestPost u pa) = APIRequestPost u <$> f pa+    params f (APIRequestPostMultipart u pa prt) =+        (\p -> APIRequestPostMultipart u p prt) <$> f pa+instance Show (APIRequest apiName responseType) where+    show (APIRequestGet u p) = "APIRequestGet " ++ show u ++ " " ++ show p+    show (APIRequestPost u p) = "APIRequestPost " ++ show u ++ " " ++ show p+    show (APIRequestPostMultipart u p _) = "APIRequestPostMultipart " ++ show u ++ " " ++ show p
+ Web/Twitter/Conduit/Status.hs view
@@ -0,0 +1,290 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE EmptyDataDecls #-}+#if __GLASGOW_HASKELL__ >= 704+{-# LANGUAGE ConstraintKinds #-}+#endif++module Web.Twitter.Conduit.Status+       (+       -- * Timelines+         StatusesMentionsTimeline+       , mentionsTimeline+       , StatusesUserTimeline+       , userTimeline+       , StatusesHomeTimeline+       , homeTimeline+       , StatusesRetweetsOfMe+       , retweetsOfMe+       -- * Tweets+       , StatusesRetweetsId+       , retweetsId+       , StatusesShowId+       , showId+       , StatusesDestroyId+       , destroyId+       , StatusesUpdate+       , update+       , StatusesRetweetId+       , retweetId+       , MediaData (..)+       , StatusesUpdateWithMedia+       , updateWithMedia+       -- , oembed+       -- , retweetersIds+       ) where++import Web.Twitter.Conduit.Base+import Web.Twitter.Conduit.Request+import Web.Twitter.Conduit.Parameters+import Web.Twitter.Conduit.Parameters.TH+import Web.Twitter.Types++import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import Network.HTTP.Client.MultipartFormData+import Network.HTTP.Conduit+import Data.Default++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> import Control.Lens++-- * Timelines++data StatusesMentionsTimeline+-- | Returns query data asks the most recent mentions for the authenticating user.+--+-- You can perform a query using 'call':+--+-- @+-- res <- 'call' 'mentionsTimeline'+-- @+--+-- >>> mentionsTimeline+-- APIRequestGet "https://api.twitter.com/1.1/statuses/mentions_timeline.json" []+mentionsTimeline :: APIRequest StatusesMentionsTimeline [Status]+mentionsTimeline = APIRequestGet (endpoint ++ "statuses/mentions_timeline.json") def+deriveHasParamInstances ''StatusesMentionsTimeline+    [ "count"+    , "since_id"+    , "max_id"+    , "trim_user"+    , "contributor_details"+    , "include_entities"+    ]++data StatusesUserTimeline+-- | Returns query data asks a collection of the most recent Tweets posted by the user indicated by the screen_name or user_id parameters.+--+-- You can perform a search query using 'call':+--+-- @+-- res <- 'call' $ 'userTimeline' ('ScreenNameParam' \"thimura\")+-- @+--+-- >>> userTimeline (ScreenNameParam "thimura")+-- APIRequestGet "https://api.twitter.com/1.1/statuses/user_timeline.json" [("screen_name","thimura")]+-- >>> userTimeline (ScreenNameParam "thimura") & includeRts ?~ True & count ?~ 200+-- APIRequestGet "https://api.twitter.com/1.1/statuses/user_timeline.json" [("count","200"),("include_rts","true"),("screen_name","thimura")]+userTimeline :: UserParam -> APIRequest StatusesUserTimeline [Status]+userTimeline q = APIRequestGet (endpoint ++ "statuses/user_timeline.json") (mkUserParam q)+deriveHasParamInstances ''StatusesUserTimeline+    [ "count"+    , "since_id"+    , "max_id"+    , "trim_user"+    , "exclude_replies"+    , "contributor_details"+    , "include_rts"+    ]++data StatusesHomeTimeline+-- | Returns query data asks a collection of the most recentTweets and retweets posted by the authenticating user and the users they follow.+--+-- You can perform a search query using 'call':+--+-- @+-- res <- 'call' 'homeTimeline'+-- @+--+-- >>> homeTimeline+-- APIRequestGet "https://api.twitter.com/1.1/statuses/home_timeline.json" []+-- >>> homeTimeline & count ?~ 200+-- APIRequestGet "https://api.twitter.com/1.1/statuses/home_timeline.json" [("count","200")]+homeTimeline :: APIRequest StatusesHomeTimeline [Status]+homeTimeline = APIRequestGet (endpoint ++ "statuses/home_timeline.json") def+deriveHasParamInstances ''StatusesHomeTimeline+    [ "count"+    , "since_id"+    , "max_id"+    , "trim_user"+    , "exclude_replies"+    , "contributor_details"+    , "include_entities"+    ]++data StatusesRetweetsOfMe+-- | Returns query data asks the most recent tweets authored by the authenticating user that have been retweeted by others.+--+-- You can perform a search query using 'call':+--+-- @+-- res <- 'call' 'retweetsOfMe'+-- @+--+-- >>> retweetsOfMe+-- APIRequestGet "https://api.twitter.com/1.1/statuses/retweets_of_me.json" []+-- >>> retweetsOfMe & count ?~ 100+-- APIRequestGet "https://api.twitter.com/1.1/statuses/retweets_of_me.json" [("count","100")]+retweetsOfMe :: APIRequest StatusesRetweetsOfMe [Status]+retweetsOfMe = APIRequestGet (endpoint ++ "statuses/retweets_of_me.json") def+deriveHasParamInstances ''StatusesRetweetsOfMe+    [ "count"+    , "since_id"+    , "max_id"+    , "trim_user"+    , "include_entities"+    , "include_user_entities"+    ]++-- * Tweets++data StatusesRetweetsId+-- | Returns query data asks the most recent tweets authored by the authenticating user that have been retweeted by others.+--+-- You can perform a search query using 'call':+--+-- @+-- res <- 'call' '$' 'retweetsId' 1234567890+-- @+--+-- >>> retweetsId 1234567890+-- APIRequestGet "https://api.twitter.com/1.1/statuses/retweets/1234567890.json" []+-- >>> retweetsId 1234567890 & count ?~ 100+-- APIRequestGet "https://api.twitter.com/1.1/statuses/retweets/1234567890.json" [("count","100")]+retweetsId :: StatusId -> APIRequest StatusesRetweetsId [RetweetedStatus]+retweetsId status_id = APIRequestGet uri def+  where uri = endpoint ++ "statuses/retweets/" ++ show status_id ++ ".json"+deriveHasParamInstances ''StatusesRetweetsId+    [ "count"+    , "trim_user"+    ]++data StatusesShowId+-- | Returns query data asks a single Tweet, specified by the id parameter.+--+-- You can perform a search query using 'call':+--+-- @+-- res <- 'call' '$' 'showId' 1234567890+-- @+--+-- >>> showId 1234567890+-- APIRequestGet "https://api.twitter.com/1.1/statuses/show/1234567890.json" []+-- >>> showId 1234567890 & includeMyRetweet ?~ True+-- APIRequestGet "https://api.twitter.com/1.1/statuses/show/1234567890.json" [("include_my_retweet","true")]+showId :: StatusId -> APIRequest StatusesShowId Status+showId status_id = APIRequestGet uri def+  where uri = endpoint ++ "statuses/show/" ++ show status_id ++ ".json"+deriveHasParamInstances ''StatusesShowId+    [ "trim_user"+    , "include_my_retweet"+    , "include_entities"+    ]++data StatusesDestroyId+-- | Returns post data which destroys the status specified by the require ID parameter.+--+-- You can perform a search query using 'call':+--+-- @+-- res <- 'call' '$' 'destroyId' 1234567890+-- @+--+-- >>> destroyId 1234567890+-- APIRequestPost "https://api.twitter.com/1.1/statuses/destroy/1234567890.json" []+destroyId :: StatusId -> APIRequest StatusesDestroyId Status+destroyId status_id = APIRequestPost uri def+  where uri = endpoint ++ "statuses/destroy/" ++ show status_id ++ ".json"+deriveHasParamInstances ''StatusesDestroyId+    [ "trim_user"+    ]++data StatusesUpdate+-- | Returns post data which updates the authenticating user's current status.+-- To upload an image to accompany the tweet, use 'updateWithMedia'.+--+-- You can perform a search query using 'call':+--+-- @+-- res <- 'call' '$' 'update' \"Hello World\"+-- @+--+-- >>> update "Hello World"+-- APIRequestPost "https://api.twitter.com/1.1/statuses/update.json" [("status","Hello World")]+-- >>> update "Hello World" & inReplyToStatusId ?~ 1234567890+-- APIRequestPost "https://api.twitter.com/1.1/statuses/update.json" [("in_reply_to_status_id","1234567890"),("status","Hello World")]+update :: T.Text -> APIRequest StatusesUpdate Status+update status = APIRequestPost uri [("status", T.encodeUtf8 status)]+  where uri = endpoint ++ "statuses/update.json"+deriveHasParamInstances ''StatusesUpdate+    [ "in_reply_to_status_id"+    -- , "lat_long"+    -- , "place_id"+    , "display_coordinates"+    , "trim_user"+    ]++data StatusesRetweetId+-- | Returns post data which retweets a tweet, specified by ID.+--+-- You can perform a search query using 'call':+--+-- @+-- res <- 'call' '$' 'retweetId' 1234567890+-- @+--+-- >>> retweetId 1234567890+-- APIRequestPost "https://api.twitter.com/1.1/statuses/retweet/1234567890.json" []+retweetId :: StatusId -> APIRequest StatusesRetweetId RetweetedStatus+retweetId status_id = APIRequestPost uri def+  where uri = endpoint ++ "statuses/retweet/" ++ show status_id ++ ".json"+deriveHasParamInstances ''StatusesRetweetId+    [ "trim_user"+    ]++data MediaData = MediaFromFile FilePath+               | MediaRequestBody FilePath RequestBody++data StatusesUpdateWithMedia+-- | Returns post data which updates the authenticating user's current status and attaches media for upload.+--+-- You can perform a search query using 'call':+--+-- @+-- res <- 'call' '$' 'updateWithMedia' \"Hello World\" ('MediaFromFile' \"/home/thimura/test.jpeg\")+-- @+--+-- >>> updateWithMedia "Hello World" (MediaFromFile "/home/fuga/test.jpeg")+-- APIRequestPostMultipart "https://api.twitter.com/1.1/statuses/update_with_media.json" [("status","Hello World")]+updateWithMedia :: T.Text+                -> MediaData+                -> APIRequest StatusesUpdateWithMedia Status+updateWithMedia tweet mediaData =+    APIRequestPostMultipart uri [("status", T.encodeUtf8 tweet)] [mediaBody mediaData]+  where+    uri = endpoint ++ "statuses/update_with_media.json"+    mediaBody (MediaFromFile fp) = partFileSource "media[]" fp+    mediaBody (MediaRequestBody filename filebody) = partFileRequestBody "media[]" filename filebody+deriveHasParamInstances ''StatusesUpdateWithMedia+    [ "possibly_sensitive"+    , "in_reply_to_status_id"+    -- , "lat_long"+    -- , "place_id"+    , "display_coordinates"+    ]
+ Web/Twitter/Conduit/Stream.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 704+{-# LANGUAGE ConstraintKinds #-}+#endif++module Web.Twitter.Conduit.Stream+       (+       -- * StreamingAPI+         userstream+       , statusesFilterByFollow+       , statusesFilterByTrack+       -- , statusesFilterByLocation+       -- , statusesSample+       -- , statusesFirehose+       -- , sitestream+       -- , sitestream'+       , stream+  ) where++import Web.Twitter.Conduit.Base+import Web.Twitter.Conduit.Monad+import Web.Twitter.Types+import Web.Twitter.Conduit.Request++import qualified Data.Conduit as C+import qualified Data.Conduit.List as CL+import qualified Data.Conduit.Internal as CI+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.ByteString as S+import Control.Monad.IO.Class+import Data.Aeson++($=+) :: MonadIO m+      => CI.ResumableSource m a+      -> CI.Conduit a m o+      -> m (CI.ResumableSource m o)+rsrc $=+ cndt = do+    (src, finalizer) <- C.unwrapResumable rsrc+    return $ CI.ResumableSource (src C.$= cndt) finalizer++stream :: (TwitterBaseM m, FromJSON responseType)+       => APIRequest apiName responseType+       -> TW m (C.ResumableSource (TW m) responseType)+stream = stream'++stream' :: (TwitterBaseM m, FromJSON value)+        => APIRequest apiName responseType+        -> TW m (C.ResumableSource (TW m) value)+stream' (APIRequestGet u pa) = do+    rsrc <- api "GET" u pa+    rsrc $=+ CL.sequence sinkFromJSON+stream' (APIRequestPost u pa) = do+    rsrc <- api "POST" u pa+    rsrc $=+ CL.sequence sinkFromJSON+stream' APIRequestPostMultipart {} =+    error "APIRequestPostMultipart is not supported by stream function."++data Userstream+userstream :: APIRequest Userstream StreamingAPI+userstream = APIRequestGet "https://userstream.twitter.com/1.1/user.json" []++statusesFilterEndpoint :: String+statusesFilterEndpoint = "https://stream.twitter.com/1.1/statuses/filter.json"++data StatusesFilter+statusesFilterByFollow :: [UserId] -> APIRequest StatusesFilter StreamingAPI+statusesFilterByFollow userIds =+    APIRequestPost statusesFilterEndpoint [("follow", S.intercalate "," . map showBS $ userIds)]++statusesFilterByTrack :: T.Text -- ^ keyword+                      -> APIRequest StatusesFilter StreamingAPI+statusesFilterByTrack keyword =+    APIRequestPost statusesFilterEndpoint [("track", T.encodeUtf8 keyword)]
+ sample/oauth_callback.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE OverloadedStrings #-}++-- Example:+--   $ export OAUTH_CONSUMER_KEY="your consumer key"+--   $ export OAUTH_CONSUMER_SECRET="your consumer secret"+--   $ runhaskell oauth_callback.hs++module Main where++import Web.Scotty+import qualified Network.HTTP.Types as HT+import Web.Twitter.Conduit hiding (text)+import Web.Authenticate.OAuth (OAuth(..), Credential(..))+import qualified Web.Authenticate.OAuth as OA+import qualified Network.HTTP.Conduit as HTTP+import qualified Data.Text.Lazy as LT+import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8+import qualified Data.Map as M+import Data.Maybe+import Data.Monoid+import Data.IORef+import Control.Monad.IO.Class+import System.Environment+import System.IO.Unsafe++callback :: String+callback = "http://localhost:3000/callback"++getTokens :: IO OAuth+getTokens = do+    consumerKey <- getEnv "OAUTH_CONSUMER_KEY"+    consumerSecret <- getEnv "OAUTH_CONSUMER_SECRET"+    return $+        twitterOAuth+        { oauthConsumerKey = S8.pack consumerKey+        , oauthConsumerSecret = S8.pack consumerSecret+        , oauthCallback = Just $ S8.pack callback+        }++type OAuthToken = S.ByteString++usersToken :: IORef (M.Map OAuthToken Credential)+usersToken = unsafePerformIO $ newIORef M.empty++takeCredential :: OAuthToken -> IORef (M.Map OAuthToken Credential) -> IO (Maybe Credential)+takeCredential k ioref =+    atomicModifyIORef ioref $ \m ->+        let (res, newm) = M.updateLookupWithKey (\_ _ -> Nothing) k m in+        (newm, res)++storeCredential :: OAuthToken -> Credential -> IORef (M.Map OAuthToken Credential) -> IO ()+storeCredential k cred ioref =+    atomicModifyIORef ioref $ \m -> (M.insert k cred m, ())++main :: IO ()+main = do+    tokens <- getTokens+    putStrLn $ "browse URL: http://localhost:3000/signIn"+    scotty 3000 $ app tokens++makeMessage :: OAuth -> Credential -> S.ByteString+makeMessage tokens (Credential cred) =+    S8.intercalate "\n"+        [ "export OAUTH_CONSUMER_KEY=\"" <> oauthConsumerKey tokens <> "\""+        , "export OAUTH_CONSUMER_SECRET=\"" <> oauthConsumerSecret tokens <> "\""+        , "export OAUTH_ACCESS_TOKEN=\"" <> fromMaybe "" (lookup "oauth_token" cred) <> "\""+        , "export OAUTH_ACCESS_SECRET=\"" <> fromMaybe "" (lookup "oauth_token_secret" cred) <> "\""+        ]++app :: OAuth -> ScottyM ()+app tokens = do+    get "/callback" $ do+        temporaryToken <- param "oauth_token"+        oauthVerifier <- param "oauth_verifier"+        mcred <- liftIO $ takeCredential temporaryToken usersToken+        case mcred of+            Just cred -> do+                accessTokens <- liftIO $ HTTP.withManager $ OA.getAccessToken tokens (OA.insert "oauth_verifier" oauthVerifier cred)+                liftIO $ print accessTokens++                let message = makeMessage tokens accessTokens+                liftIO . S8.putStrLn $ message+                text . LT.pack . S8.unpack $ message++            Nothing -> do+                status HT.status404+                text "temporary token is not found"++    get "/signIn" $ do+        cred <- liftIO $ HTTP.withManager $ OA.getTemporaryCredential tokens+        case lookup "oauth_token" $ unCredential cred of+            Just temporaryToken -> do+                liftIO $ storeCredential temporaryToken cred usersToken+                let url = OA.authorizeUrl tokens cred+                redirect $ LT.pack url+            Nothing -> do+                status HT.status500+                text "Failed to obtain the temporary token."
+ sample/oauth_pin.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}++-- Example:+--   $ export OAUTH_CONSUMER_KEY="your consumer key"+--   $ export OAUTH_CONSUMER_SECRET="your consumer secret"+--   $ runhaskell oauth_pin.hs++module Main where++import Web.Twitter.Conduit+import Web.Authenticate.OAuth as OA+import Network.HTTP.Conduit+import qualified Data.Conduit as C+import qualified Data.ByteString.Char8 as S8+import Data.Maybe+import Data.Monoid+import Control.Monad.Trans.Control+import Control.Monad.IO.Class+import System.Environment+import System.IO (hFlush, stdout)++getTokens :: IO OAuth+getTokens = do+    consumerKey <- getEnv "OAUTH_CONSUMER_KEY"+    consumerSecret <- getEnv "OAUTH_CONSUMER_SECRET"+    return $+        twitterOAuth+        { oauthConsumerKey = S8.pack consumerKey+        , oauthConsumerSecret = S8.pack consumerSecret+        , oauthCallback = Just "oob"+        }++authorize :: (MonadBaseControl IO m, C.MonadResource m)+          => OAuth -- ^ OAuth Consumer key and secret+          -> Manager+          -> m Credential+authorize oauth mgr = do+    cred <- OA.getTemporaryCredential oauth mgr+    let url = OA.authorizeUrl oauth cred+    pin <- getPIN url+    OA.getAccessToken oauth (OA.insert "oauth_verifier" pin cred) mgr+  where+    getPIN url = liftIO $ do+        putStrLn $ "browse URL: " ++ url+        putStr "> what was the PIN twitter provided you with? "+        hFlush stdout+        S8.getLine++main :: IO ()+main = do+    tokens <- getTokens+    Credential cred <- liftIO $ withManager $ authorize tokens+    print cred++    S8.putStrLn . S8.intercalate "\n" $+        [ "export OAUTH_CONSUMER_KEY=\"" <> oauthConsumerKey tokens <> "\""+        , "export OAUTH_CONSUMER_SECRET=\"" <> oauthConsumerSecret tokens <> "\""+        , "export OAUTH_ACCESS_TOKEN=\"" <> fromMaybe "" (lookup "oauth_token" cred) <> "\""+        , "export OAUTH_ACCESS_SECRET=\"" <> fromMaybe "" (lookup "oauth_token_secret" cred) <> "\""+        ]
+ sample/simple.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}++module Main where++import Web.Twitter.Conduit+import Control.Lens+import Web.Authenticate.OAuth (OAuth(..), Credential(..))+import qualified Web.Authenticate.OAuth as OA+import Network.HTTP.Conduit+import qualified Data.Conduit as C+import qualified Data.Conduit.List as CL+import qualified Data.Text as T+import qualified Data.Text.IO as T+import qualified Data.ByteString.Char8 as B8+import Data.Default+import Control.Monad.Logger+import Control.Monad.Trans.Control+import Control.Monad.Trans.Resource+import Control.Monad.IO.Class+import System.IO (hFlush, stdout)++tokens :: OAuth+tokens = twitterOAuth+    { oauthConsumerKey = error "You MUST specify oauthConsumerKey parameter."+    , oauthConsumerSecret = error "You MUST specify oauthConsumerSecret parameter."+    }++authorize :: (MonadBaseControl IO m, C.MonadResource m)+          => OAuth -- ^ OAuth Consumer key and secret+          -> (String -> m String) -- ^ PIN prompt+          -> Manager+          -> m Credential+authorize oauth getPIN mgr = do+    cred <- OA.getTemporaryCredential oauth mgr+    let url = OA.authorizeUrl oauth cred+    pin <- getPIN url+    OA.getAccessToken oauth (OA.insert "oauth_verifier" (B8.pack pin) cred) mgr++withCredential :: (MonadLogger m, MonadBaseControl IO m, MonadIO m) => TW (ResourceT m) a -> m a+withCredential task = do+    cred <- liftIO $ withManager $ \mgr -> authorize tokens getPIN mgr+    let env = setCredential tokens cred def+    runTW env task+  where+    getPIN url = liftIO $ do+        putStrLn $ "browse URL: " ++ url+        putStr "> what was the PIN twitter provided you with? "+        hFlush stdout+        getLine++main :: IO ()+main = runNoLoggingT . withCredential $ do+    liftIO . putStrLn $ "# your home timeline (up to 100 tweets):"+    sourceWithMaxId homeTimeline+        C.$= CL.isolate 100+        C.$$ CL.mapM_ $ \status -> liftIO $ do+            T.putStrLn $ T.concat [ T.pack . show $ status ^. statusId+                                  , ": "+                                  , status ^. statusUser . userScreenName+                                  , ": "+                                  , status ^. statusText+                                  ]
+ sample/userstream.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE PatternGuards #-}++import qualified Data.Conduit as C+import qualified Data.Conduit.List as CL+import qualified Data.Text.IO as T+import qualified Data.Text as T+import System.Process+import Control.Monad+import Control.Monad.IO.Class+import Control.Applicative+import System.FilePath+import System.Directory+import Data.Conduit+import qualified Data.Conduit.Binary as CB+import Network.HTTP.Conduit++import Web.Twitter.Conduit+import Common+import Control.Lens++ensureDirectoryExist :: FilePath -> IO FilePath+ensureDirectoryExist dir = do+    createDirectoryIfMissing True dir+    return dir++confdir :: IO FilePath+confdir = fmap (</> ".twitter-conduit") getHomeDirectory >>= ensureDirectoryExist++iconPath :: IO FilePath+iconPath = (</> "icons") <$> confdir >>= ensureDirectoryExist++main :: IO ()+main = runTwitterFromEnv' $ do+    src <- stream userstream+    src C.$$+- CL.mapM_ (^! act (liftIO . printTL))++showStatus :: AsStatus s => s -> T.Text+showStatus s = T.concat [ s ^. user . userScreenName+                        , ":"+                        , s ^. text+                        ]++printTL :: StreamingAPI -> IO ()+printTL (SStatus s) = T.putStrLn . showStatus $ s+printTL (SRetweetedStatus s) = T.putStrLn $ T.concat [ s ^. user . userScreenName+                                                     , ": RT @"+                                                     , showStatus (s ^. rsRetweetedStatus)+                                                     ]+printTL (SEvent event)+    | (event^.evEvent) == "favorite" || (event^.evEvent) == "unfavorite",+      Just (ETStatus st) <- (event ^. evTargetObject) = do+          let (fromUser, fromIcon) = evUserInfo (event^.evSource)+              (toUser, _toIcon) = evUserInfo (event^.evTarget)+              evUserInfo (ETUser u) = (u ^. userScreenName, u ^. userProfileImageURL)+              evUserInfo _ = ("", Nothing)+              header = T.concat [ event ^. evEvent, "[", fromUser, " -> ", toUser, "]"]+          T.putStrLn $ T.concat [ header, " :: ", showStatus st ]+          icon <- case fromIcon of+              Just iconUrl -> Just <$> fetchIcon (T.unpack fromUser) (T.unpack iconUrl)+              Nothing -> return Nothing+          notifySend header (showStatus st) icon+printTL s = print s++notifySend :: T.Text -> T.Text -> Maybe FilePath -> IO ()+notifySend header content icon = do+    let ic = maybe [] (\i -> ["-i", i]) icon+    void $ rawSystem "notify-send" $ [T.unpack header, T.unpack content] ++ ic++fetchIcon :: String -- ^ screen name+          -> String -- ^ icon url+          -> IO String+fetchIcon sn url = do+    ipath <- iconPath+    let fname = ipath </> sn ++ "__" ++ takeFileName url+    exists <- doesFileExist fname+    unless exists $ withManager $ \mgr -> do+        req <- liftIO $ parseUrl url+        body <- http req mgr+        responseBody body $$+- CB.sinkFile fname+    return fname
+ tests/doctests.hs view
@@ -0,0 +1,28 @@+module Main where++import Test.DocTest+import System.Directory+import System.FilePath+import Data.List+import Control.Monad+import Control.Applicative++main = do+  sources <- findSources "Web"+  doctest $ args ++ sources+  where+    args = [ "-i."+           , "-idist/build/autogen"+           , "-optP-include"+           , "-optPdist/build/autogen/cabal_macros.h"+           ]++findSources :: FilePath -> IO [FilePath]+findSources dir = filter (isSuffixOf ".hs") <$> go dir+  where+    go dir = do+      (dirs, files) <- listFiles dir+      (files ++) . concat <$> mapM go dirs+    listFiles dir = do+      entries <- map (dir </>) . filter (`notElem` [".", ".."]) <$> getDirectoryContents dir+      (,) <$> filterM doesDirectoryExist entries <*> filterM doesFileExist entries
+ tests/hlint.hs view
@@ -0,0 +1,12 @@+module Main where++import Control.Monad+import Language.Haskell.HLint+import System.Environment+import System.Exit++main :: IO ()+main = do+  args <- getArgs+  hints <- hlint $ ["Web", "--cpp-define=HLINT", "--cpp-ansi"] ++ args+  unless (null hints) exitFailure
+ twitter-conduit.cabal view
@@ -0,0 +1,194 @@+name:              twitter-conduit+version:           0.0.1+license:           BSD3+license-file:      LICENSE+author:            HATTORI Hiroki, Hideyuki Tanaka, Takahiro HIMURA+maintainer:        Takahiro HIMURA <taka@himura.jp>+synopsis:          Twitter API package with conduit interface and Streaming API support.+category:          Web, Conduit+stability:         Experimental+cabal-version:     >= 1.8+build-type:        Simple+homepage:          https://github.com/himura/twitter-conduit++description:+  This package provides bindings to Twitter's APIs (see <https://dev.twitter.com/>).+  .+  This package uses http-conduit package for access Twitter API (see <http://hackage.haskell.org/package/http-conduit>).+  This package also depends to twitter-types package (see <http://hackage.haskell.org/package/twitter-types>).+  .+  You can find basic examples in the <https://github.com/himura/twitter-conduit/tree/master/sample> directory.+  .+  This package is under development. If you find something that has not been implemented yet, please send a pull request or open an issue on GitHub.++source-repository head+  type: git+  location: git://github.com/himura/twitter-conduit.git++flag build-samples+  description: build samples+  default: False++library+  ghc-options: -Wall++  build-depends:+      base >= 4 && < 5+    , transformers >= 0.2.2+    , transformers-base+    , template-haskell+    , lifted-base >= 0.1+    , monad-control >= 0.3+    , lens >= 4.0+    , authenticate-oauth >= 1.3+    , resourcet >= 0.4.3+    , conduit >= 1.0+    , failure >= 0.2+    , monad-logger+    , shakespeare-text+    , http-types+    , http-conduit >= 2.0+    , http-client-multipart+    , aeson >= 0.7+    , attoparsec >= 0.10+    , attoparsec-conduit >= 1.0+    , data-default >= 0.3+    , bytestring >= 0.9+    , text >= 0.11+    , containers+    , time+    , twitter-types >= 0.2++  exposed-modules:+    Web.Twitter.Conduit+    Web.Twitter.Conduit.Base+    Web.Twitter.Conduit.Cursor+    Web.Twitter.Conduit.Api+    Web.Twitter.Conduit.Error+    Web.Twitter.Conduit.Monad+    Web.Twitter.Conduit.Stream+    Web.Twitter.Conduit.Status+    Web.Twitter.Conduit.Request+    Web.Twitter.Conduit.Parameters+    Web.Twitter.Conduit.Parameters.Internal+    Web.Twitter.Conduit.Parameters.TH++  other-modules:++test-suite hlint+  type: exitcode-stdio-1.0+  main-is: hlint.hs+  hs-source-dirs: tests++  build-depends:+      base+    , hlint >= 1.7++test-suite doctests+  type: exitcode-stdio-1.0+  main-is: doctests.hs+  hs-source-dirs: tests++  build-depends:+      base+    , filepath+    , directory+    , doctest++executable simple+  main-is: simple.hs+  hs-source-dirs: sample/++  if !flag(build-samples)+    buildable: False+  else+    build-depends:+        base >= 4.0 && < 5+      , transformers-base+      , transformers+      , monad-control+      , lens+      , bytestring+      , text+      , data-default+      , resourcet+      , conduit+      , http-conduit+      , monad-logger+      , authenticate-oauth+      , twitter-conduit++executable userstream+  main-is: userstream.hs+  hs-source-dirs: sample/++  if !flag(build-samples)+    buildable: False+  else+    build-depends:+        base >= 4.0 && < 5+      , containers+      , transformers-base+      , transformers+      , monad-control+      , bytestring+      , text+      , filepath+      , directory+      , network+      , process+      , case-insensitive+      , lens+      , aeson+      , data-default+      , resourcet+      , conduit+      , http-conduit+      , monad-logger+      , authenticate-oauth+      , twitter-conduit++executable oauth_callback+  main-is: oauth_callback.hs+  hs-source-dirs: sample/++  if !flag(build-samples)+    buildable: False+  else+    build-depends:+        base >= 4.0 && < 5+      , containers+      , transformers-base+      , transformers+      , monad-control+      , bytestring+      , text+      , resourcet+      , conduit+      , http-types+      , http-conduit+      , authenticate-oauth+      , twitter-conduit+      , scotty >= 0.7++executable oauth_pin+  main-is: oauth_pin.hs+  hs-source-dirs: sample/++  if !flag(build-samples)+    buildable: False+  else+    build-depends:+        base >= 4.0 && < 5+      , containers+      , transformers-base+      , transformers+      , monad-control+      , bytestring+      , text+      , resourcet+      , conduit+      , http-types+      , http-conduit+      , authenticate-oauth+      , twitter-conduit