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 +26/−0
- Setup.hs +3/−0
- Web/Twitter/Conduit.hs +12/−0
- Web/Twitter/Conduit/Api.hs +442/−0
- Web/Twitter/Conduit/Base.hs +211/−0
- Web/Twitter/Conduit/Cursor.hs +61/−0
- Web/Twitter/Conduit/Error.hs +15/−0
- Web/Twitter/Conduit/Monad.hs +109/−0
- Web/Twitter/Conduit/Parameters.hs +110/−0
- Web/Twitter/Conduit/Parameters/Internal.hs +63/−0
- Web/Twitter/Conduit/Parameters/TH.hs +59/−0
- Web/Twitter/Conduit/Request.hs +72/−0
- Web/Twitter/Conduit/Status.hs +290/−0
- Web/Twitter/Conduit/Stream.hs +77/−0
- sample/oauth_callback.hs +99/−0
- sample/oauth_pin.hs +61/−0
- sample/simple.hs +63/−0
- sample/userstream.hs +82/−0
- tests/doctests.hs +28/−0
- tests/hlint.hs +12/−0
- twitter-conduit.cabal +194/−0
+ 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