twitter-conduit-0.5.0/Web/0000755000000000000000000000000013652033050013561 5ustar0000000000000000twitter-conduit-0.5.0/Web/Twitter/0000755000000000000000000000000013652033050015223 5ustar0000000000000000twitter-conduit-0.5.0/Web/Twitter/Conduit/0000755000000000000000000000000013652033050016630 5ustar0000000000000000twitter-conduit-0.5.0/Web/Twitter/Conduit/Request/0000755000000000000000000000000013652033050020260 5ustar0000000000000000twitter-conduit-0.5.0/sample/0000755000000000000000000000000013652033152014330 5ustar0000000000000000twitter-conduit-0.5.0/sample/common/0000755000000000000000000000000013652033050015615 5ustar0000000000000000twitter-conduit-0.5.0/tests/0000755000000000000000000000000013652033050014206 5ustar0000000000000000twitter-conduit-0.5.0/Web/Twitter/Conduit.hs0000644000000000000000000001412513652033050017167 0ustar0000000000000000{-# OPTIONS_GHC -fno-warn-unused-imports #-} -- | -- Module: Web.Twitter.Conduit -- Copyright: (c) 2014 Takahiro Himura -- License: BSD -- Maintainer: Takahiro Himura -- Stability: experimental -- Portability: portable -- -- A client library for Twitter APIs (see ). module Web.Twitter.Conduit ( -- * How to use this library -- $howto -- * Re-exports module Web.Twitter.Conduit.Api , module Web.Twitter.Conduit.Cursor , module Web.Twitter.Conduit.Request , module Web.Twitter.Conduit.Response , module Web.Twitter.Conduit.Stream , module Web.Twitter.Conduit.Types -- * 'Web.Twitter.Conduit.Base' , call , call' , callWithResponse , callWithResponse' , sourceWithMaxId , sourceWithMaxId' , sourceWithCursor , sourceWithCursor' , sourceWithSearchResult , sourceWithSearchResult' -- * 'Web.Twitter.Conduit.Parameters' , ListParam(..) , MediaData(..) , UserListParam(..) , UserParam(..) -- * re-exports , OAuth (..) , Credential (..) , def , Manager , newManager , tlsManagerSettings ) where import Web.Twitter.Conduit.Api import Web.Twitter.Conduit.Base import Web.Twitter.Conduit.Cursor import Web.Twitter.Conduit.Parameters import Web.Twitter.Conduit.Request import Web.Twitter.Conduit.Response import Web.Twitter.Conduit.Stream import Web.Twitter.Conduit.Types import Data.Default (def) import Network.HTTP.Conduit (Manager, newManager, tlsManagerSettings) import Web.Authenticate.OAuth -- for haddock import Data.Conduit import qualified Data.Conduit.List as CL import qualified Data.Text as T import qualified Data.Text.IO as T import Control.Monad.IO.Class import Control.Lens {-# ANN module "HLint: ignore Use import/export shortcut" #-} -- $howto -- -- The main module of twitter-conduit is "Web.Twitter.Conduit". -- This library cooperate with , -- , -- and . -- All of following examples import modules as below: -- -- > {-# LANGUAGE OverloadedStrings #-} -- > -- > import Web.Twitter.Conduit -- > import Web.Twitter.Types.Lens -- > import Data.Conduit -- > import qualified Data.Conduit.List as CL -- > import qualified Data.Text as T -- > import qualified Data.Text.IO as T -- > import Control.Monad.IO.Class -- > import Control.Lens -- -- First, you should obtain consumer token and secret from , -- and prepare 'OAuth' variables as follows: -- -- @ -- tokens :: 'OAuth' -- tokens = 'twitterOAuth' -- { 'oauthConsumerKey' = \"YOUR CONSUMER KEY\" -- , 'oauthConsumerSecret' = \"YOUR CONSUMER SECRET\" -- } -- @ -- -- Second, you should obtain access token and secret. -- You can find examples obtaining those tokens in -- , -- for instance, -- , and -- . -- If you need more information, see . -- -- You should set an access token to 'Credential' variable: -- -- @ -- credential :: 'Credential' -- credential = 'Credential' -- [ (\"oauth_token\", \"YOUR ACCESS TOKEN\") -- , (\"oauth_token_secret\", \"YOUR ACCESS TOKEN SECRET\") -- ] -- @ -- -- You should also set up the 'TWToken' and 'TWInfo' variables as below: -- -- @ -- twInfo :: 'TWInfo' -- twInfo = 'def' -- { 'twToken' = 'def' { 'twOAuth' = tokens, 'twCredential' = credential } -- , 'twProxy' = Nothing -- } -- @ -- -- Or, simply as follows: -- -- > twInfo = setCredential tokens credential def -- -- Twitter API requests are performed by 'call' function. -- For example, -- could be obtained by: -- -- @ -- mgr \<- 'newManager' 'tlsManagerSettings' -- timeline \<- 'call' twInfo mgr 'homeTimeline' -- @ -- -- The response of 'call' function is wrapped by the suitable type of -- . -- In this case, /timeline/ has a type of ['Status']. -- If you need /raw/ JSON Value which is parsed by , -- use 'call'' to obtain it. -- -- By default, the response of -- includes 20 tweets, and you can change the number of tweets by the /count/ parameter. -- -- @ -- timeline \<- 'call' twInfo mgr '$' 'homeTimeline' '&' #count '?~' 200 -- @ -- -- If you need more statuses, you can obtain those with multiple API requests. -- This library provides the wrapper for multiple requests with conduit interfaces. -- For example, if you intend to fetch the all friends information, -- you may perform multiple API requests with changing cursor parameter, -- or use the conduit wrapper 'sourceWithCursor' as below: -- -- @ -- friends \<- 'sourceWithCursor' twInfo mgr ('friendsList' ('ScreenNameParam' \"thimura\") '&' #count '?~' 200) '$$' 'CL.consume' -- @ -- -- Statuses APIs, for instance, 'homeTimeline', are also wrapped by 'sourceWithMaxId'. -- -- For example, you can print 1000 tweets from your home timeline, as below: -- -- @ -- main :: IO () -- main = do -- mgr \<- 'newManager' 'tlsManagerSettings' -- 'sourceWithMaxId' twInfo mgr 'homeTimeline' -- $= CL.isolate 60 -- $$ CL.mapM_ $ \\status -> liftIO $ do -- T.putStrLn $ T.concat [ T.pack . show $ status ^. statusId -- , \": \" -- , status ^. statusUser . userScreenName -- , \": \" -- , status ^. statusText -- ] -- @ twitter-conduit-0.5.0/Web/Twitter/Conduit/Lens.hs0000644000000000000000000000444313652033050020072 0ustar0000000000000000{-# LANGUAGE RankNTypes #-} module Web.Twitter.Conduit.Lens ( -- * 'TT.Response' TT.Response , responseStatus , responseBody , responseHeaders -- * 'TT.TwitterErrorMessage' , TT.TwitterErrorMessage , twitterErrorMessage , twitterErrorCode -- * 'TT.WithCursor' , TT.WithCursor , previousCursor , nextCursor , contents -- * Re-exports , TT.TwitterError(..) , TT.CursorKey (..) , TT.IdsCursorKey , TT.UsersCursorKey , TT.ListsCursorKey ) where import Control.Lens import Data.Text (Text) import Network.HTTP.Types (Status, ResponseHeaders) import qualified Web.Twitter.Conduit.Cursor as TT import qualified Web.Twitter.Conduit.Response as TT -- * Lenses for 'TT.Response' responseStatus :: forall responseType. Lens' (TT.Response responseType) Status responseStatus afb s = (\b -> s { TT.responseStatus = b }) <$> afb (TT.responseStatus s) responseHeaders :: forall responseType. Lens' (TT.Response responseType) ResponseHeaders responseHeaders afb s = (\b -> s {TT.responseHeaders = b }) <$> afb (TT.responseHeaders s) responseBody :: forall a b. Lens (TT.Response a) (TT.Response b) a b responseBody afb s = (\b -> s { TT.responseBody = b }) <$> afb (TT.responseBody s) -- * Lenses for 'TT.TwitterErrorMessage' twitterErrorCode :: Lens' TT.TwitterErrorMessage Int twitterErrorCode afb s = (\b -> s { TT.twitterErrorCode = b }) <$> afb (TT.twitterErrorCode s) twitterErrorMessage :: Lens' TT.TwitterErrorMessage Text twitterErrorMessage afb s = (\b -> s { TT.twitterErrorMessage = b }) <$> afb (TT.twitterErrorMessage s) -- * Lenses for 'TT.WithCursor' previousCursor :: forall cursorType cursorKey wrapped. Lens' (TT.WithCursor cursorType cursorKey wrapped) (Maybe cursorType) previousCursor afb s = (\b -> s { TT.previousCursor = b }) <$> afb (TT.previousCursor s) nextCursor :: forall cursorType cursorKey wrapped. Lens' (TT.WithCursor cursorType cursorKey wrapped) (Maybe cursorType) nextCursor afb s = (\b -> s { TT.nextCursor = b }) <$> afb (TT.nextCursor s) contents :: forall cursorType cursorKey a b. Lens (TT.WithCursor cursorType cursorKey a) (TT.WithCursor cursorType cursorKey b) [a] [b] contents afb s = (\b -> s { TT.contents = b }) <$> afb (TT.contents s) twitter-conduit-0.5.0/Web/Twitter/Conduit/Types.hs0000644000000000000000000000357113652033050020276 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} module Web.Twitter.Conduit.Types ( TWToken (..) , TWInfo (..) , twitterOAuth , setCredential ) where import Data.Default import Data.Typeable (Typeable) import Web.Authenticate.OAuth import Network.HTTP.Conduit data TWToken = TWToken { twOAuth :: OAuth , twCredential :: Credential } deriving (Show, Read, Eq, Typeable) instance Default TWToken where def = TWToken twitterOAuth (Credential []) data TWInfo = TWInfo { twToken :: TWToken , twProxy :: Maybe Proxy } deriving (Show, Read, Eq, Typeable) 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 } -- | 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 } twitter-conduit-0.5.0/Web/Twitter/Conduit/Api.hs0000644000000000000000000011237413652033050017705 0ustar0000000000000000{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeOperators #-} module Web.Twitter.Conduit.Api ( -- * Status statusesMentionsTimeline , statusesUserTimeline , statusesHomeTimeline , statusesRetweetsOfMe , statusesRetweetsId , statusesShowId , statusesDestroyId , statusesUpdate , statusesRetweetId , statusesUpdateWithMedia , statusesLookup -- * Search , SearchTweets , searchTweets , search -- * Direct Messages , DirectMessages , directMessages , DirectMessagesSent , directMessagesSent , DirectMessagesShow , directMessagesShow , DirectMessagesDestroy , directMessagesDestroy , DirectMessagesNew , directMessagesNew -- * Friends & Followers , FriendshipsNoRetweetsIds , friendshipsNoRetweetsIds , FriendsIds , friendsIds , FollowersIds , followersIds , FriendshipsIncoming , friendshipsIncoming , FriendshipsOutgoing , friendshipsOutgoing , FriendshipsCreate , friendshipsCreate , FriendshipsDestroy , friendshipsDestroy -- , friendshipsUpdate -- , friendshipsShow , FriendsList , friendsList , FollowersList , followersList -- , friendshipsLookup -- * Users -- , accountSettings , AccountVerifyCredentials , accountVerifyCredentials -- , accountSettingsUpdate -- , accountUpdateDeliveryDevice , AccountUpdateProfile , accountUpdateProfile -- , accountUpdateProfileBackgroundImage -- , accountUpdateProfileColors -- , accoutUpdateProfileImage -- , blocksList -- , blocksIds -- , blocksCreate -- , blocksDestroy , UsersLookup , usersLookup , UsersShow , usersShow -- , usersSearch -- , usersContributees -- , usersContributors -- , accuntRemoveProfileBanner -- , accuntUpdateProfileBanner -- , usersProfileBanner -- , mutesUsersCreate -- , mutesUsersDestroy -- , mutesUsersIds -- , mutesUsersList -- * Suggested Users -- , usersSuggestionsSlug -- , usersSuggestions -- , usersSuggestionsSlugMembers -- * Favorites , FavoritesList , favoritesList , FavoritesDestroy , favoritesDestroy , FavoritesCreate , favoritesCreate -- * Lists -- , listsList , ListsStatuses , listsStatuses , ListsMembersDestroy , listsMembersDestroy , ListsMemberships , listsMemberships , ListsSubscribers , listsSubscribers -- , listsSubscribersCreate -- , listsSubscribersShow -- , listsSubscribersDestroy , ListsMembersCreateAll , listsMembersCreateAll -- , listsMembersShow , ListsMembers , listsMembers , ListsMembersCreate , listsMembersCreate , ListsDestroy , listsDestroy , ListsUpdate , listsUpdate , ListsCreate , listsCreate , ListsShow , listsShow , ListsSubscriptions , listsSubscriptions , ListsMembersDestroyAll , listsMembersDestroyAll , ListsOwnerships , listsOwnerships -- * Saved Searches -- savedSearchesList -- savedSearchesShowId -- savedSearchesCreate -- savedSearchesDestroyId -- * Places & Geo -- geoIdPlaceId -- geoReverseGeocode -- geoSearch -- geoSimilarPlaces -- geoPlace -- * media , MediaUpload , mediaUpload ) where import Web.Twitter.Conduit.Base import Web.Twitter.Conduit.Cursor import Web.Twitter.Conduit.Parameters import Web.Twitter.Conduit.Request import Web.Twitter.Conduit.Request.Internal import qualified Web.Twitter.Conduit.Status as Status import Web.Twitter.Types import Network.HTTP.Client.MultipartFormData import qualified Data.Text as T import Data.Default import Data.Time.Calendar (Day) import Data.Aeson -- $setup -- >>> :set -XOverloadedStrings -XOverloadedLabels -- >>> import Control.Lens -- | Returns search query. -- -- You can perform a search query using 'call': -- -- @ -- res <- 'call' ('searchTweets' \"search text\") -- 'print' $ res ^. 'searchResultStatuses' -- @ -- -- >>> searchTweets "search text" -- APIRequest "GET" "https://api.twitter.com/1.1/search/tweets.json" [("q","search text")] -- >>> searchTweets "search text" & #lang ?~ "ja" & #count ?~ 100 -- APIRequest "GET" "https://api.twitter.com/1.1/search/tweets.json" [("count","100"),("lang","ja"),("q","search text")] searchTweets :: T.Text -- ^ search string -> APIRequest SearchTweets (SearchResult [Status]) searchTweets q = APIRequest "GET" (endpoint ++ "search/tweets.json") [("q", PVString q)] type SearchTweets = '[ "lang" ':= T.Text , "locale" ':= T.Text , "count" ':= Integer , "until" ':= Day , "since_id" ':= Integer , "max_id" ':= Integer , "include_entities" ':= Bool ] -- | Alias of 'searchTweets', for backward compatibility search :: T.Text -- ^ search string -> APIRequest SearchTweets (SearchResult [Status]) search = searchTweets {-# DEPRECATED search "Please use Web.Twitter.Conduit.searchTweets" #-} -- | Returns query data which asks recent direct messages sent to the authenticating user. -- -- You can perform a query using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'directMessages' '&' #count '?~' 50 -- @ -- -- >>> directMessages -- APIRequest "GET" "https://api.twitter.com/1.1/direct_messages/events/list.json" [] -- >>> directMessages & #count ?~ 50 -- APIRequest "GET" "https://api.twitter.com/1.1/direct_messages/events/list.json" [("count","50")] directMessages :: APIRequest DirectMessages (WithCursor T.Text EventsCursorKey DirectMessage) directMessages = APIRequest "GET" (endpoint ++ "direct_messages/events/list.json") def type DirectMessages = '[ "count" ':= Integer , "include_entities" ':= Bool , "skip_status" ':= Bool , "full_text" ':= Bool , "cursor" ':= T.Text ] -- | Returns query data which asks recent direct messages sent by the authenticating user. -- -- You can perform a query using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'directMessagesSent' '&' #count '?~' 100 -- @ -- -- >>> directMessagesSent -- APIRequest "GET" "https://api.twitter.com/1.1/direct_messages/sent.json" [] -- >>> directMessagesSent & #count ?~ 100 -- APIRequest "GET" "https://api.twitter.com/1.1/direct_messages/sent.json" [("count","100")] directMessagesSent :: APIRequest DirectMessagesSent [DirectMessage] directMessagesSent = APIRequest "GET" (endpoint ++ "direct_messages/sent.json") def type DirectMessagesSent = '[ "since_id" ':= Integer , "max_id" ':= Integer , "count" ':= Integer , "include_entities" ':= Bool , "page" ':= Integer , "skip_status" ':= Bool , "full_text" ':= Bool ] -- | Returns query data which asks a single direct message, specified by an id parameter. -- -- You can perform a query using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'directMessagesShow' 1234567890 -- @ -- -- >>> directMessagesShow 1234567890 -- APIRequest "GET" "https://api.twitter.com/1.1/direct_messages/show.json" [("id","1234567890")] directMessagesShow :: StatusId -> APIRequest DirectMessagesShow DirectMessage directMessagesShow sId = APIRequest "GET" (endpoint ++ "direct_messages/show.json") [("id", PVInteger sId)] type DirectMessagesShow = '[ "full_text" ':= Bool ] -- | Returns post data which destroys the direct message specified in the required ID parameter. -- -- You can perform a query using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'directMessagesDestroy' 1234567890 -- @ -- -- >>> directMessagesDestroy 1234567890 -- APIRequest "DELETE" "https://api.twitter.com/1.1/direct_messages/events/destroy.json" [("id","1234567890")] directMessagesDestroy :: StatusId -> APIRequest DirectMessagesDestroy NoContent directMessagesDestroy sId = APIRequest "DELETE" (endpoint ++ "direct_messages/events/destroy.json") [("id", PVInteger sId)] type DirectMessagesDestroy = EmptyParams newtype DirectMessagesNewResponse = DirectMessagesNewResponse { directMessageBody :: DirectMessage } deriving (Show, Eq) instance FromJSON DirectMessagesNewResponse where parseJSON = withObject "DirectMessagesNewResponse" $ \o -> DirectMessagesNewResponse <$> o .: "event" -- | 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' twInfo mgr '$' 'directMessagesNew' (ScreenNameParam \"thimura\") \"Hello DM\" -- @ -- -- >>> directMessagesNew 69179963 "Hello thimura! by UserId" -- APIRequestJSON "POST" "https://api.twitter.com/1.1/direct_messages/events/new.json" [] directMessagesNew :: RecipientId -> T.Text -> APIRequest DirectMessagesNew DirectMessagesNewResponse directMessagesNew up msg = APIRequestJSON "POST" (endpoint ++ "direct_messages/events/new.json") [] body where body = object [ "event" .= object [ "type" .= ("message_create" :: String) , "message_create" .= object [ "target" .= object ["recipient_id" .= up] , "message_data" .= object ["text" .= msg] ] ] ] type DirectMessagesNew = EmptyParams type RecipientId = Integer -- | Returns a collection of user_ids that the currently authenticated user does not want to receive retweets from. -- -- You can perform a request using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'friendshipsNoRetweetsIds' -- @ -- -- >>> friendshipsNoRetweetsIds -- APIRequest "GET" "https://api.twitter.com/1.1/friendships/no_retweets/ids.json" [] friendshipsNoRetweetsIds :: APIRequest FriendshipsNoRetweetsIds [UserId] friendshipsNoRetweetsIds = APIRequest "GET" (endpoint ++ "friendships/no_retweets/ids.json") [] type FriendshipsNoRetweetsIds = EmptyParams -- | 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' twInfo mgr '$' 'friendsIds' ('ScreenNameParam' \"thimura\") -- @ -- -- Or, you can iterate with 'sourceWithCursor': -- -- @ -- 'sourceWithCursor' ('friendsIds' ('ScreenNameParam' \"thimura\")) $$ CL.consume -- @ -- -- >>> friendsIds (ScreenNameParam "thimura") -- APIRequest "GET" "https://api.twitter.com/1.1/friends/ids.json" [("screen_name","thimura")] -- >>> friendsIds (ScreenNameParam "thimura") & #count ?~ 5000 -- APIRequest "GET" "https://api.twitter.com/1.1/friends/ids.json" [("count","5000"),("screen_name","thimura")] friendsIds :: UserParam -> APIRequest FriendsIds (WithCursor Integer IdsCursorKey UserId) friendsIds q = APIRequest "GET" (endpoint ++ "friends/ids.json") (mkUserParam q) type FriendsIds = '[ "count" ':= Integer , "cursor" ':= Integer ] -- | 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' twInfo mgr '$' 'followersIds' ('ScreenNameParam' \"thimura\") -- @ -- -- Or, you can iterate with 'sourceWithCursor': -- -- @ -- 'sourceWithCursor' ('followersIds' ('ScreenNameParam' \"thimura\")) $$ CL.consume -- @ -- -- >>> followersIds (ScreenNameParam "thimura") -- APIRequest "GET" "https://api.twitter.com/1.1/followers/ids.json" [("screen_name","thimura")] -- >>> followersIds (ScreenNameParam "thimura") & #count ?~ 5000 -- APIRequest "GET" "https://api.twitter.com/1.1/followers/ids.json" [("count","5000"),("screen_name","thimura")] followersIds :: UserParam -> APIRequest FollowersIds (WithCursor Integer IdsCursorKey UserId) followersIds q = APIRequest "GET" (endpoint ++ "followers/ids.json") (mkUserParam q) type FollowersIds = '[ "count" ':= Integer , "cursor" ':= Integer ] -- | Returns a collection of numeric IDs for every user who has a pending request to follow the authenticating user. -- -- You can perform a request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'friendshipsIncoming' -- @ -- -- Or, you can iterate with 'sourceWithCursor': -- -- @ -- 'sourceWithCursor' 'friendshipsIncoming' $$ CL.consume -- @ -- -- >>> friendshipsIncoming -- APIRequest "GET" "https://api.twitter.com/1.1/friendships/incoming.json" [] friendshipsIncoming :: APIRequest FriendshipsIncoming (WithCursor Integer IdsCursorKey UserId) friendshipsIncoming = APIRequest "GET" (endpoint ++ "friendships/incoming.json") def type FriendshipsIncoming = '[ "cursor" ':= Integer ] -- | Returns a collection of numeric IDs for every protected user for whom the authenticating user has a pending follow request. -- -- You can perform a request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'friendshipsOutgoing' -- @ -- -- Or, you can iterate with 'sourceWithCursor': -- -- @ -- 'sourceWithCursor' 'friendshipsOutgoing' $$ CL.consume -- @ -- -- >>> friendshipsOutgoing -- APIRequest "GET" "https://api.twitter.com/1.1/friendships/outgoing.json" [] friendshipsOutgoing :: APIRequest FriendshipsOutgoing (WithCursor Integer IdsCursorKey UserId) friendshipsOutgoing = APIRequest "GET" (endpoint ++ "friendships/outgoing.json") def type FriendshipsOutgoing = '[ "cursor" ':= Integer ] -- | Returns post data which follows the user specified in the ID parameter. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'friendshipsCreate' ('ScreenNameParam' \"thimura\") -- @ -- -- >>> friendshipsCreate (ScreenNameParam "thimura") -- APIRequest "POST" "https://api.twitter.com/1.1/friendships/create.json" [("screen_name","thimura")] -- >>> friendshipsCreate (UserIdParam 69179963) -- APIRequest "POST" "https://api.twitter.com/1.1/friendships/create.json" [("user_id","69179963")] friendshipsCreate :: UserParam -> APIRequest FriendshipsCreate User friendshipsCreate user = APIRequest "POST" (endpoint ++ "friendships/create.json") (mkUserParam user) type FriendshipsCreate = '[ "follow" ':= Bool ] -- | Returns post data which unfollows the user specified in the ID parameter. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'friendshipsDestroy' ('ScreenNameParam' \"thimura\") -- @ -- -- >>> friendshipsDestroy (ScreenNameParam "thimura") -- APIRequest "POST" "https://api.twitter.com/1.1/friendships/destroy.json" [("screen_name","thimura")] -- >>> friendshipsDestroy (UserIdParam 69179963) -- APIRequest "POST" "https://api.twitter.com/1.1/friendships/destroy.json" [("user_id","69179963")] friendshipsDestroy :: UserParam -> APIRequest FriendshipsDestroy User friendshipsDestroy user = APIRequest "POST" (endpoint ++ "friendships/destroy.json") (mkUserParam user) type FriendshipsDestroy = EmptyParams -- | 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' twInfo mgr '$' 'friendsList' ('ScreenNameParam' \"thimura\") -- @ -- -- Or, you can iterate with 'sourceWithCursor': -- -- @ -- 'sourceWithCursor' ('friendsList' ('ScreenNameParam' \"thimura\")) $$ CL.consume -- @ -- -- >>> friendsList (ScreenNameParam "thimura") -- APIRequest "GET" "https://api.twitter.com/1.1/friends/list.json" [("screen_name","thimura")] -- >>> friendsList (UserIdParam 69179963) -- APIRequest "GET" "https://api.twitter.com/1.1/friends/list.json" [("user_id","69179963")] friendsList :: UserParam -> APIRequest FriendsList (WithCursor Integer UsersCursorKey User) friendsList q = APIRequest "GET" (endpoint ++ "friends/list.json") (mkUserParam q) type FriendsList = '[ "count" ':= Integer , "cursor" ':= Integer , "skip_status" ':= Bool , "include_user_entities" ':= Bool ] -- | Returns query data which asks a cursored collection of user objects for users following the specified user. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'followersList' ('ScreenNameParam' \"thimura\") -- @ -- -- Or, you can iterate with 'sourceWithCursor': -- -- @ -- 'sourceWithCursor' ('followersList' ('ScreenNameParam' \"thimura\")) $$ CL.consume -- @ -- -- >>> followersList (ScreenNameParam "thimura") -- APIRequest "GET" "https://api.twitter.com/1.1/followers/list.json" [("screen_name","thimura")] -- >>> followersList (UserIdParam 69179963) -- APIRequest "GET" "https://api.twitter.com/1.1/followers/list.json" [("user_id","69179963")] followersList :: UserParam -> APIRequest FollowersList (WithCursor Integer UsersCursorKey User) followersList q = APIRequest "GET" (endpoint ++ "followers/list.json") (mkUserParam q) type FollowersList = '[ "count" ':= Integer , "cursor" ':= Integer , "skip_status" ':= Bool , "include_user_entities" ':= Bool ] -- | Returns query data asks that the credential is valid. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'accountVerifyCredentials' -- @ -- -- >>> accountVerifyCredentials -- APIRequest "GET" "https://api.twitter.com/1.1/account/verify_credentials.json" [] accountVerifyCredentials :: APIRequest AccountVerifyCredentials User accountVerifyCredentials = APIRequest "GET" (endpoint ++ "account/verify_credentials.json") [] type AccountVerifyCredentials = '[ "include_entities" ':= Bool , "skip_status" ':= Bool , "include_email" ':= Bool ] -- | Returns user object with updated fields. -- Note that while no specific parameter is required, you need to provide at least one parameter before executing the query. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'accountUpdateProfile' & #url ?~ \"http://www.example.com\" -- @ -- -- >>> accountUpdateProfile & #url ?~ "http://www.example.com" -- APIRequest "POST" "https://api.twitter.com/1.1/account/update_profile.json" [("url","http://www.example.com")] accountUpdateProfile :: APIRequest AccountUpdateProfile User accountUpdateProfile = APIRequest "POST" (endpoint ++ "account/update_profile.json") [] type AccountUpdateProfile = '[ "include_entities" ':= Bool , "skip_status" ':= Bool , "name" ':= T.Text , "url" ':= URIString , "location" ':= T.Text , "description" ':= T.Text , "profile_link_color" ':= T.Text ] -- | Returns query data asks user objects. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'usersLookup' ('ScreenNameListParam' [\"thimura\", \"twitterapi\"]) -- @ -- -- >>> usersLookup (ScreenNameListParam ["thimura", "twitterapi"]) -- APIRequest "GET" "https://api.twitter.com/1.1/users/lookup.json" [("screen_name","thimura,twitterapi")] usersLookup :: UserListParam -> APIRequest UsersLookup [User] usersLookup q = APIRequest "GET" (endpoint ++ "users/lookup.json") (mkUserListParam q) type UsersLookup = '[ "include_entities" ':= Bool ] -- | Returns query data asks the user specified by user id or screen name parameter. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'usersShow' ('ScreenNameParam' \"thimura\") -- @ -- -- >>> usersShow (ScreenNameParam "thimura") -- APIRequest "GET" "https://api.twitter.com/1.1/users/show.json" [("screen_name","thimura")] usersShow :: UserParam -> APIRequest UsersShow User usersShow q = APIRequest "GET" (endpoint ++ "users/show.json") (mkUserParam q) type UsersShow = '[ "include_entities" ':= Bool ] -- | Returns the 20 most recent Tweets favorited by the specified user. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'favoritesList' (ScreenNameParam \"thimura\") -- @ -- -- >>> favoritesList Nothing -- APIRequest "GET" "https://api.twitter.com/1.1/favorites/list.json" [] -- >>> favoritesList (Just (ScreenNameParam "thimura")) -- APIRequest "GET" "https://api.twitter.com/1.1/favorites/list.json" [("screen_name","thimura")] -- >>> favoritesList (Just (UserIdParam 69179963)) -- APIRequest "GET" "https://api.twitter.com/1.1/favorites/list.json" [("user_id","69179963")] favoritesList :: Maybe UserParam -> APIRequest FavoritesList [Status] favoritesList mbuser = APIRequest "GET" (endpoint ++ "favorites/list.json") (mkParam mbuser) where mkParam Nothing = [] mkParam (Just usr) = mkUserParam usr type FavoritesList = '[ "count" ':= Integer , "since_id" ':= Integer , "max_id" ':= Integer , "include_entities" ':= Bool ] -- | 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' twInfo mgr '$' 'favoritesCreate' 1234567890 -- @ -- -- >>> favoritesCreate 1234567890 -- APIRequest "POST" "https://api.twitter.com/1.1/favorites/create.json" [("id","1234567890")] favoritesCreate :: StatusId -> APIRequest FavoritesCreate Status favoritesCreate sid = APIRequest "POST" (endpoint ++ "favorites/create.json") [("id", PVInteger sid)] type FavoritesCreate = '[ "include_entities" ':= Bool ] -- | Returns post data unfavorites the status specified in the ID paramter as the authenticating user. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'favoritesDestroy' 1234567890 -- @ -- -- >>> favoritesDestroy 1234567890 -- APIRequest "POST" "https://api.twitter.com/1.1/favorites/destroy.json" [("id","1234567890")] favoritesDestroy :: StatusId -> APIRequest FavoritesDestroy Status favoritesDestroy sid = APIRequest "POST" (endpoint ++ "favorites/destroy.json") [("id", PVInteger sid)] type FavoritesDestroy = '[ "include_entities" ':= Bool ] -- | Returns the query parameter which fetches a timeline of tweets authored by members of the specified list. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsStatuses' ('ListNameParam' "thimura/haskell") -- @ -- -- If you need more statuses, you can obtain those by using 'sourceWithMaxId': -- @ -- res <- sourceWithMaxId ('listsStatuses' ('ListNameParam' "thimura/haskell") & #count ?~ 200) $$ CL.take 1000 -- @ -- -- >>> listsStatuses (ListNameParam "thimura/haskell") -- APIRequest "GET" "https://api.twitter.com/1.1/lists/statuses.json" [("slug","haskell"),("owner_screen_name","thimura")] -- >>> listsStatuses (ListIdParam 20849097) -- APIRequest "GET" "https://api.twitter.com/1.1/lists/statuses.json" [("list_id","20849097")] listsStatuses :: ListParam -> APIRequest ListsStatuses [Status] listsStatuses q = APIRequest "GET" (endpoint ++ "lists/statuses.json") (mkListParam q) type ListsStatuses = '[ "since_id" ':= Integer , "max_id" ':= Integer , "count" ':= Integer , "include_entities" ':= Bool , "include_rts" ':= Bool ] -- | Returns the post parameter which removes the specified member from the list. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsMembersDestroy' ('ListNameParam' "thimura/haskell") ('ScreenNameParam' "thimura") -- @ -- -- >>> listsMembersDestroy (ListNameParam "thimura/haskell") (ScreenNameParam "thimura") -- APIRequest "POST" "https://api.twitter.com/1.1/lists/members/destroy.json" [("slug","haskell"),("owner_screen_name","thimura"),("screen_name","thimura")] -- >>> listsMembersDestroy (ListIdParam 20849097) (UserIdParam 69179963) -- APIRequest "POST" "https://api.twitter.com/1.1/lists/members/destroy.json" [("list_id","20849097"),("user_id","69179963")] listsMembersDestroy :: ListParam -> UserParam -> APIRequest ListsMembersDestroy List listsMembersDestroy list user = APIRequest "POST" (endpoint ++ "lists/members/destroy.json") (mkListParam list ++ mkUserParam user) type ListsMembersDestroy = EmptyParams -- | Returns the request parameters which asks the lists the specified user has been added to. -- If 'UserParam' are not provided, the memberships for the authenticating user are returned. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsMemberships' ('ListNameParam' "thimura/haskell") -- @ -- -- >>> listsMemberships Nothing -- APIRequest "GET" "https://api.twitter.com/1.1/lists/memberships.json" [] -- >>> listsMemberships (Just (ScreenNameParam "thimura")) -- APIRequest "GET" "https://api.twitter.com/1.1/lists/memberships.json" [("screen_name","thimura")] -- >>> listsMemberships (Just (UserIdParam 69179963)) -- APIRequest "GET" "https://api.twitter.com/1.1/lists/memberships.json" [("user_id","69179963")] listsMemberships :: Maybe UserParam -> APIRequest ListsMemberships (WithCursor Integer ListsCursorKey List) listsMemberships q = APIRequest "GET" (endpoint ++ "lists/memberships.json") $ maybe [] mkUserParam q type ListsMemberships = '[ "count" ':= Integer , "cursor" ':= Integer ] -- | Returns the request parameter which asks the subscribers of the specified list. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsSubscribers' ('ListNameParam' "thimura/haskell") -- @ -- -- >>> listsSubscribers (ListNameParam "thimura/haskell") -- APIRequest "GET" "https://api.twitter.com/1.1/lists/subscribers.json" [("slug","haskell"),("owner_screen_name","thimura")] -- >>> listsSubscribers (ListIdParam 20849097) -- APIRequest "GET" "https://api.twitter.com/1.1/lists/subscribers.json" [("list_id","20849097")] listsSubscribers :: ListParam -> APIRequest ListsSubscribers (WithCursor Integer UsersCursorKey User) listsSubscribers q = APIRequest "GET" (endpoint ++ "lists/subscribers.json") (mkListParam q) type ListsSubscribers = '[ "count" ':= Integer , "cursor" ':= Integer , "skip_status" ':= Bool ] -- | Returns the request parameter which obtains a collection of the lists the specified user is subscribed to. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsSubscriptions' ('ListNameParam' "thimura/haskell") -- @ -- -- >>> listsSubscriptions Nothing -- APIRequest "GET" "https://api.twitter.com/1.1/lists/subscriptions.json" [] -- >>> listsSubscriptions (Just (ScreenNameParam "thimura")) -- APIRequest "GET" "https://api.twitter.com/1.1/lists/subscriptions.json" [("screen_name","thimura")] -- >>> listsSubscriptions (Just (UserIdParam 69179963)) -- APIRequest "GET" "https://api.twitter.com/1.1/lists/subscriptions.json" [("user_id","69179963")] listsSubscriptions :: Maybe UserParam -> APIRequest ListsSubscriptions (WithCursor Integer ListsCursorKey List) listsSubscriptions q = APIRequest "GET" (endpoint ++ "lists/subscriptions.json") $ maybe [] mkUserParam q type ListsSubscriptions = '[ "count" ':= Integer , "cursor" ':= Integer ] -- | Returns the request parameter which asks the lists owned by the specified Twitter user. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsOwnerships' ('ListNameParam' "thimura/haskell") -- @ -- -- >>> listsOwnerships Nothing -- APIRequest "GET" "https://api.twitter.com/1.1/lists/ownerships.json" [] -- >>> listsOwnerships (Just (ScreenNameParam "thimura")) -- APIRequest "GET" "https://api.twitter.com/1.1/lists/ownerships.json" [("screen_name","thimura")] -- >>> listsOwnerships (Just (UserIdParam 69179963)) -- APIRequest "GET" "https://api.twitter.com/1.1/lists/ownerships.json" [("user_id","69179963")] listsOwnerships :: Maybe UserParam -> APIRequest ListsOwnerships (WithCursor Integer ListsCursorKey List) listsOwnerships q = APIRequest "GET" (endpoint ++ "lists/ownerships.json") $ maybe [] mkUserParam q type ListsOwnerships = '[ "count" ':= Integer , "cursor" ':= Integer ] -- | Adds multiple members to a list. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsMembersCreateAll' ('ListNameParam' "thimura/haskell") ('ScreenNameListParam' [\"thimura\", \"twitterapi\"]) -- @ -- -- >>> listsMembersCreateAll (ListNameParam "thimura/haskell") (ScreenNameListParam ["thimura", "twitterapi"]) -- APIRequest "POST" "https://api.twitter.com/1.1/lists/members/create_all.json" [("slug","haskell"),("owner_screen_name","thimura"),("screen_name","thimura,twitterapi")] -- >>> listsMembersCreateAll (ListIdParam 20849097) (UserIdListParam [69179963, 6253282]) -- APIRequest "POST" "https://api.twitter.com/1.1/lists/members/create_all.json" [("list_id","20849097"),("user_id","69179963,6253282")] listsMembersCreateAll :: ListParam -> UserListParam -> APIRequest ListsMembersCreateAll List listsMembersCreateAll list users = APIRequest "POST" (endpoint ++ "lists/members/create_all.json") (mkListParam list ++ mkUserListParam users) type ListsMembersCreateAll = EmptyParams -- | Adds multiple members to a list. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsMembersDestroyAll' ('ListNameParam' "thimura/haskell") ('ScreenNameListParam' [\"thimura\", \"twitterapi\"]) -- @ -- -- >>> listsMembersDestroyAll (ListNameParam "thimura/haskell") (ScreenNameListParam ["thimura", "twitterapi"]) -- APIRequest "POST" "https://api.twitter.com/1.1/lists/members/destroy_all.json" [("slug","haskell"),("owner_screen_name","thimura"),("screen_name","thimura,twitterapi")] -- >>> listsMembersDestroyAll (ListIdParam 20849097) (UserIdListParam [69179963, 6253282]) -- APIRequest "POST" "https://api.twitter.com/1.1/lists/members/destroy_all.json" [("list_id","20849097"),("user_id","69179963,6253282")] listsMembersDestroyAll :: ListParam -> UserListParam -> APIRequest ListsMembersDestroyAll List listsMembersDestroyAll list users = APIRequest "POST" (endpoint ++ "lists/members/destroy_all.json") (mkListParam list ++ mkUserListParam users) type ListsMembersDestroyAll = EmptyParams -- | Returns query data asks the members of the specified list. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsMembers' ('ListNameParam' "thimura/haskell") -- @ -- -- >>> listsMembers (ListNameParam "thimura/haskell") -- APIRequest "GET" "https://api.twitter.com/1.1/lists/members.json" [("slug","haskell"),("owner_screen_name","thimura")] -- >>> listsMembers (ListIdParam 20849097) -- APIRequest "GET" "https://api.twitter.com/1.1/lists/members.json" [("list_id","20849097")] listsMembers :: ListParam -> APIRequest ListsMembers (WithCursor Integer UsersCursorKey User) listsMembers q = APIRequest "GET" (endpoint ++ "lists/members.json") (mkListParam q) type ListsMembers = '[ "count" ':= Integer , "cursor" ':= Integer , "skip_status" ':= Bool ] -- | Returns the post parameter which adds a member to a list. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsMembersCreate' ('ListNameParam' "thimura/haskell") ('ScreenNameParam' "thimura") -- @ -- -- >>> listsMembersCreate (ListNameParam "thimura/haskell") (ScreenNameParam "thimura") -- APIRequest "POST" "https://api.twitter.com/1.1/lists/members/create.json" [("slug","haskell"),("owner_screen_name","thimura"),("screen_name","thimura")] -- >>> listsMembersCreate (ListIdParam 20849097) (UserIdParam 69179963) -- APIRequest "POST" "https://api.twitter.com/1.1/lists/members/create.json" [("list_id","20849097"),("user_id","69179963")] listsMembersCreate :: ListParam -> UserParam -> APIRequest ListsMembersCreate List listsMembersCreate list user = APIRequest "POST" (endpoint ++ "lists/members/create.json") (mkListParam list ++ mkUserParam user) type ListsMembersCreate = EmptyParams -- | Returns the post parameter which deletes the specified list. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsDestroy' ('ListNameParam' "thimura/haskell") -- @ -- -- >>> listsDestroy (ListNameParam "thimura/haskell") -- APIRequest "POST" "https://api.twitter.com/1.1/lists/destroy.json" [("slug","haskell"),("owner_screen_name","thimura")] -- >>> listsDestroy (ListIdParam 20849097) -- APIRequest "POST" "https://api.twitter.com/1.1/lists/destroy.json" [("list_id","20849097")] listsDestroy :: ListParam -> APIRequest ListsDestroy List listsDestroy list = APIRequest "POST" (endpoint ++ "lists/destroy.json") (mkListParam list) type ListsDestroy = EmptyParams -- | Returns the post parameter which updates the specified list. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsUpdate' ('ListNameParam' "thimura/haskell") True (Just "Haskellers") -- @ -- -- >>> listsUpdate (ListNameParam "thimura/haskell") True (Just "Haskellers") -- APIRequest "POST" "https://api.twitter.com/1.1/lists/update.json" [("slug","haskell"),("owner_screen_name","thimura"),("description","Haskellers"),("mode","public")] listsUpdate :: ListParam -> Bool -- ^ is public -> Maybe T.Text -- ^ description -> APIRequest ListsUpdate List listsUpdate list isPublic description = APIRequest "POST" (endpoint ++ "lists/update.json") (mkListParam list ++ p') where p = [("mode", PVString . mode $ isPublic)] p' = maybe id (\d -> (("description", PVString d):)) description p mode True = "public" mode False = "private" type ListsUpdate = EmptyParams -- | Returns the post parameter which creates a new list for the authenticated user. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsCreate' ('ListNameParam' "thimura/haskell") -- @ -- -- >>> listsCreate "haskell" True Nothing -- APIRequest "POST" "https://api.twitter.com/1.1/lists/create.json" [("name","haskell"),("mode","public")] -- >>> listsCreate "haskell" False Nothing -- APIRequest "POST" "https://api.twitter.com/1.1/lists/create.json" [("name","haskell"),("mode","private")] -- >>> listsCreate "haskell" True (Just "Haskellers") -- APIRequest "POST" "https://api.twitter.com/1.1/lists/create.json" [("description","Haskellers"),("name","haskell"),("mode","public")] listsCreate :: T.Text -- ^ list name -> Bool -- ^ whether public(True) or private(False) -> Maybe T.Text -- ^ the description to give the list -> APIRequest ListsCreate List listsCreate name isPublic description = APIRequest "POST" (endpoint ++ "lists/create.json") p' where p = [("name", PVString name), ("mode", PVString . mode $ isPublic)] p' = maybe id (\d -> (("description", PVString d):)) description p mode True = "public" mode False = "private" type ListsCreate = EmptyParams -- | Returns the request parameter which asks the specified list. -- -- You can perform request by using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'listsShow' ('ListNameParam' "thimura/haskell") -- @ -- -- >>> listsShow (ListNameParam "thimura/haskell") -- APIRequest "GET" "https://api.twitter.com/1.1/lists/show.json" [("slug","haskell"),("owner_screen_name","thimura")] -- >>> listsShow (ListIdParam 20849097) -- APIRequest "GET" "https://api.twitter.com/1.1/lists/show.json" [("list_id","20849097")] listsShow :: ListParam -> APIRequest ListsShow List listsShow q = APIRequest "GET" (endpoint ++ "lists/show.json") (mkListParam q) type ListsShow = EmptyParams -- | Upload media and returns the media data. -- -- You can update your status with multiple media by calling 'mediaUpload' and 'update' successively. -- -- First, you should upload media with 'mediaUpload': -- -- @ -- res1 <- 'call' twInfo mgr '$' 'mediaUpload' ('MediaFromFile' \"\/path\/to\/upload\/file1.png\") -- res2 <- 'call' twInfo mgr '$' 'mediaUpload' ('MediaRequestBody' \"file2.png\" \"[.. file body ..]\") -- @ -- -- and then collect the resulting media IDs and update your status by calling 'update': -- -- @ -- 'call' twInfo mgr '$' 'update' \"Hello World\" '&' #media_ids '?~' ['uploadedMediaId' res1, 'uploadedMediaId' res2] -- @ -- -- See: -- -- >>> mediaUpload (MediaFromFile "/home/test/test.png") -- APIRequestMultipart "POST" "https://upload.twitter.com/1.1/media/upload.json" [] mediaUpload :: MediaData -> APIRequest MediaUpload UploadedMedia mediaUpload mediaData = APIRequestMultipart "POST" uri [] [mediaBody mediaData] where uri = "https://upload.twitter.com/1.1/media/upload.json" mediaBody (MediaFromFile fp) = partFileSource "media" fp mediaBody (MediaRequestBody filename filebody) = partFileRequestBody "media" filename filebody type MediaUpload = EmptyParams statusesMentionsTimeline :: APIRequest Status.StatusesMentionsTimeline [Status] statusesMentionsTimeline = Status.mentionsTimeline statusesUserTimeline :: UserParam -> APIRequest Status.StatusesUserTimeline [Status] statusesUserTimeline = Status.userTimeline statusesHomeTimeline :: APIRequest Status.StatusesHomeTimeline [Status] statusesHomeTimeline = Status.homeTimeline statusesRetweetsOfMe :: APIRequest Status.StatusesRetweetsOfMe [Status] statusesRetweetsOfMe = Status.retweetsOfMe statusesRetweetsId :: StatusId -> APIRequest Status.StatusesRetweetsId [RetweetedStatus] statusesRetweetsId = Status.retweetsId statusesShowId :: StatusId -> APIRequest Status.StatusesShowId Status statusesShowId = Status.showId statusesDestroyId :: StatusId -> APIRequest Status.StatusesDestroyId Status statusesDestroyId = Status.destroyId statusesUpdate :: T.Text -> APIRequest Status.StatusesUpdate Status statusesUpdate = Status.update statusesRetweetId :: StatusId -> APIRequest Status.StatusesRetweetId RetweetedStatus statusesRetweetId = Status.retweetId statusesUpdateWithMedia :: T.Text -> MediaData -> APIRequest Status.StatusesUpdateWithMedia Status statusesUpdateWithMedia = Status.updateWithMedia statusesLookup :: [StatusId] -> APIRequest Status.StatusesLookup [Status] statusesLookup = Status.lookup twitter-conduit-0.5.0/Web/Twitter/Conduit/Stream.hs0000644000000000000000000001011113652033050020411 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} module Web.Twitter.Conduit.Stream ( -- * StreamingAPI Userstream , userstream , StatusesFilter , FilterParameter (..) , statusesFilter , statusesFilterByFollow , statusesFilterByTrack -- , statusesFilterByLocation -- , statusesSample -- , statusesFirehose -- , sitestream -- , sitestream' , stream , stream' ) where import Web.Twitter.Conduit.Types import Web.Twitter.Conduit.Base import Web.Twitter.Types import Web.Twitter.Conduit.Request import Web.Twitter.Conduit.Request.Internal import Web.Twitter.Conduit.Response import Control.Monad.Catch import Control.Monad.IO.Class import Control.Monad.Trans.Resource (MonadResource) import Data.Aeson import qualified Data.ByteString.Char8 as S8 import Data.Char import qualified Data.Conduit as C import qualified Data.Conduit.List as CL import qualified Data.List as L import qualified Data.Text as T import qualified Network.HTTP.Conduit as HTTP stream :: ( MonadResource m , FromJSON responseType , MonadThrow m ) => TWInfo -> HTTP.Manager -> APIRequest apiName responseType -> m (C.ConduitM () responseType m ()) stream = stream' stream' :: ( MonadResource m , FromJSON value , MonadThrow m ) => TWInfo -> HTTP.Manager -> APIRequest apiName responseType -> m (C.ConduitM () value m ()) stream' info mgr req = do rsrc <- getResponse info mgr =<< liftIO (makeRequest req) return $ responseBody rsrc C..| CL.sequence sinkFromJSONIgnoreSpaces where sinkFromJSONIgnoreSpaces = CL.filter (not . S8.all isSpace) C..| sinkFromJSON userstream :: APIRequest Userstream StreamingAPI userstream = APIRequest "GET" "https://userstream.twitter.com/1.1/user.json" [] type Userstream = '[ "language" ':= T.Text , "filter_level" ':= T.Text , "stall_warnings" ':= Bool , "replies" ':= T.Text ] -- https://dev.twitter.com/streaming/overview/request-parameters data FilterParameter = Follow [UserId] | Track [T.Text] -- | Returns statuses/filter.json API query data. -- -- >>> statusesFilter [Follow [1,2,3]] -- APIRequest "POST" "https://stream.twitter.com/1.1/statuses/filter.json" [("follow","1,2,3")] -- >>> statusesFilter [Track ["haskell","functional"]] -- APIRequest "POST" "https://stream.twitter.com/1.1/statuses/filter.json" [("track","haskell,functional")] -- >>> statusesFilter [Follow [1,2,3],Track ["haskell","functional"]] -- APIRequest "POST" "https://stream.twitter.com/1.1/statuses/filter.json" [("follow","1,2,3"),("track","haskell,functional")] statusesFilter :: [FilterParameter] -> APIRequest StatusesFilter StreamingAPI statusesFilter fs = APIRequest "POST" statusesFilterEndpoint (L.map paramToQueryItem fs) paramToQueryItem :: FilterParameter -> APIQueryItem paramToQueryItem (Follow userIds) = ("follow", PVIntegerArray userIds) paramToQueryItem (Track texts) = ("track", PVStringArray texts) statusesFilterEndpoint :: String statusesFilterEndpoint = "https://stream.twitter.com/1.1/statuses/filter.json" -- | Returns statuses/filter.json API query data. -- -- >>> statusesFilterByFollow [1,2,3] -- APIRequest "POST" "https://stream.twitter.com/1.1/statuses/filter.json" [("follow","1,2,3")] statusesFilterByFollow :: [UserId] -> APIRequest StatusesFilter StreamingAPI statusesFilterByFollow userIds = statusesFilter [Follow userIds] -- | Returns statuses/filter.json API query data. -- -- >>> statusesFilterByTrack "haskell" -- APIRequest "POST" "https://stream.twitter.com/1.1/statuses/filter.json" [("track","haskell")] statusesFilterByTrack :: T.Text -- ^ keyword -> APIRequest StatusesFilter StreamingAPI statusesFilterByTrack keyword = statusesFilter [Track [keyword]] type StatusesFilter = '[ "language" ':= T.Text , "filter_level" ':= T.Text , "stall_warnings" ':= Bool ] twitter-conduit-0.5.0/Web/Twitter/Conduit/Status.hs0000644000000000000000000002472113652033050020455 0ustar0000000000000000{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeOperators #-} 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 , StatusesLookup , lookup ) where import Prelude hiding ( lookup ) import Web.Twitter.Conduit.Base import Web.Twitter.Conduit.Request import Web.Twitter.Conduit.Request.Internal import Web.Twitter.Conduit.Parameters import Web.Twitter.Types import qualified Data.Text as T import Network.HTTP.Client.MultipartFormData import Data.Default -- $setup -- >>> :set -XOverloadedStrings -XOverloadedLabels -- >>> import Control.Lens -- * Timelines -- | Returns query data asks the most recent mentions for the authenticating user. -- -- You can perform a query using 'call': -- -- @ -- res <- 'call' 'mentionsTimeline' -- @ -- -- >>> mentionsTimeline -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/mentions_timeline.json" [] mentionsTimeline :: APIRequest StatusesMentionsTimeline [Status] mentionsTimeline = APIRequest "GET" (endpoint ++ "statuses/mentions_timeline.json") def type StatusesMentionsTimeline = '[ "count" ':= Integer , "since_id" ':= Integer , "max_id" ':= Integer , "trim_user" ':= Bool , "contributor_details" ':= Bool , "include_entities" ':= Bool , "tweet_mode" ':= T.Text ] -- | 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") -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/user_timeline.json" [("screen_name","thimura")] -- >>> userTimeline (ScreenNameParam "thimura") & #include_rts ?~ True & #count ?~ 200 -- APIRequest "GET" "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 = APIRequest "GET" (endpoint ++ "statuses/user_timeline.json") (mkUserParam q) type StatusesUserTimeline = '[ "count" ':= Integer , "since_id" ':= Integer , "max_id" ':= Integer , "trim_user" ':= Bool , "exclude_replies" ':= Bool , "contributor_details" ':= Bool , "include_rts" ':= Bool , "tweet_mode" ':= T.Text ] -- | 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 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/home_timeline.json" [] -- >>> homeTimeline & #count ?~ 200 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/home_timeline.json" [("count","200")] homeTimeline :: APIRequest StatusesHomeTimeline [Status] homeTimeline = APIRequest "GET" (endpoint ++ "statuses/home_timeline.json") def type StatusesHomeTimeline = '[ "count" ':= Integer , "since_id" ':= Integer , "max_id" ':= Integer , "trim_user" ':= Bool , "exclude_replies" ':= Bool , "contributor_details" ':= Bool , "include_entities" ':= Bool , "tweet_mode" ':= T.Text ] -- | 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 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets_of_me.json" [] -- >>> retweetsOfMe & #count ?~ 100 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets_of_me.json" [("count","100")] retweetsOfMe :: APIRequest StatusesRetweetsOfMe [Status] retweetsOfMe = APIRequest "GET" (endpoint ++ "statuses/retweets_of_me.json") def type StatusesRetweetsOfMe = '[ "count" ':= Integer , "since_id" ':= Integer , "max_id" ':= Integer , "trim_user" ':= Bool , "include_entities" ':= Bool , "include_user_entities" ':= Bool , "tweet_mode" ':= T.Text ] -- * Tweets -- | Returns query data that asks for the most recent retweets of the specified tweet -- -- You can perform a search query using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'retweetsId' 1234567890 -- @ -- -- >>> retweetsId 1234567890 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets/1234567890.json" [] -- >>> retweetsId 1234567890 & #count ?~ 100 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets/1234567890.json" [("count","100")] retweetsId :: StatusId -> APIRequest StatusesRetweetsId [RetweetedStatus] retweetsId status_id = APIRequest "GET" uri def where uri = endpoint ++ "statuses/retweets/" ++ show status_id ++ ".json" type StatusesRetweetsId = '[ "count" ':= Integer , "trim_user" ':= Bool ] -- | Returns query data asks a single Tweet, specified by the id parameter. -- -- You can perform a search query using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'showId' 1234567890 -- @ -- -- >>> showId 1234567890 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/show/1234567890.json" [] -- >>> showId 1234567890 & #include_my_retweet ?~ True -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/show/1234567890.json" [("include_my_retweet","true")] showId :: StatusId -> APIRequest StatusesShowId Status showId status_id = APIRequest "GET" uri def where uri = endpoint ++ "statuses/show/" ++ show status_id ++ ".json" type StatusesShowId = '[ "trim_user" ':= Bool , "include_my_retweet" ':= Bool , "include_entities" ':= Bool , "include_ext_alt_text" ':= Bool , "tweet_mode" ':= T.Text ] -- | Returns post data which destroys the status specified by the require ID parameter. -- -- You can perform a search query using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'destroyId' 1234567890 -- @ -- -- >>> destroyId 1234567890 -- APIRequest "POST" "https://api.twitter.com/1.1/statuses/destroy/1234567890.json" [] destroyId :: StatusId -> APIRequest StatusesDestroyId Status destroyId status_id = APIRequest "POST" uri def where uri = endpoint ++ "statuses/destroy/" ++ show status_id ++ ".json" type StatusesDestroyId = '[ "trim_user" ':= Bool , "tweet_mode" ':= T.Text ] -- | 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' twInfo mgr '$' 'update' \"Hello World\" -- @ -- -- >>> update "Hello World" -- APIRequest "POST" "https://api.twitter.com/1.1/statuses/update.json" [("status","Hello World")] -- >>> update "Hello World" & #in_reply_to_status_id ?~ 1234567890 -- APIRequest "POST" "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 = APIRequest "POST" uri [("status", PVString status)] where uri = endpoint ++ "statuses/update.json" type StatusesUpdate = '[ "in_reply_to_status_id" ':= Integer -- , "lat_long" -- , "place_id" , "display_coordinates" ':= Bool , "trim_user" ':= Bool , "media_ids" ':= [Integer] , "tweet_mode" ':= T.Text ] -- | Returns post data which retweets a tweet, specified by ID. -- -- You can perform a search query using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'retweetId' 1234567890 -- @ -- -- >>> retweetId 1234567890 -- APIRequest "POST" "https://api.twitter.com/1.1/statuses/retweet/1234567890.json" [] retweetId :: StatusId -> APIRequest StatusesRetweetId RetweetedStatus retweetId status_id = APIRequest "POST" uri def where uri = endpoint ++ "statuses/retweet/" ++ show status_id ++ ".json" type StatusesRetweetId = '[ "trim_user" ':= Bool ] -- | 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' twInfo mgr '$' 'updateWithMedia' \"Hello World\" ('MediaFromFile' \"/home/thimura/test.jpeg\") -- @ -- -- >>> updateWithMedia "Hello World" (MediaFromFile "/home/fuga/test.jpeg") -- APIRequestMultipart "POST" "https://api.twitter.com/1.1/statuses/update_with_media.json" [("status","Hello World")] updateWithMedia :: T.Text -> MediaData -> APIRequest StatusesUpdateWithMedia Status updateWithMedia tweet mediaData = APIRequestMultipart "POST" uri [("status", PVString 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 type StatusesUpdateWithMedia = '[ "possibly_sensitive" ':= Bool , "in_reply_to_status_id" ':= Integer -- , "lat_long" -- , "place_id" , "display_coordinates" ':= Bool , "tweet_mode" ':= T.Text ] -- | Returns fully-hydrated tweet objects for up to 100 tweets per request, as specified by comma-separated values passed to the id parameter. -- -- You can perform a request using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'lookup' [20, 432656548536401920] -- @ -- -- >>> lookup [10] -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/lookup.json" [("id","10")] -- >>> lookup [10, 432656548536401920] -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/lookup.json" [("id","10,432656548536401920")] -- >>> lookup [10, 432656548536401920] & #include_entities ?~ True -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/lookup.json" [("include_entities","true"),("id","10,432656548536401920")] lookup :: [StatusId] -> APIRequest StatusesLookup [Status] lookup ids = APIRequest "GET" (endpoint ++ "statuses/lookup.json") [("id", PVIntegerArray ids)] type StatusesLookup = '[ "include_entities" ':= Bool , "trim_user" ':= Bool , "map" ':= Bool , "tweet_mode" ':= T.Text ] twitter-conduit-0.5.0/Web/Twitter/Conduit/Base.hs0000644000000000000000000003407013652033050020042 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} module Web.Twitter.Conduit.Base ( ResponseBodyType (..) , NoContent , getResponse , call , call' , callWithResponse , callWithResponse' , checkResponse , sourceWithMaxId , sourceWithMaxId' , sourceWithCursor , sourceWithCursor' , sourceWithSearchResult , sourceWithSearchResult' , endpoint , makeRequest , sinkJSON , sinkFromJSON ) where import Web.Twitter.Conduit.Cursor import Web.Twitter.Conduit.Request import Web.Twitter.Conduit.Request.Internal import Web.Twitter.Conduit.Response import Web.Twitter.Conduit.Types import Web.Twitter.Types.Lens import Control.Lens import Control.Monad (void) import Control.Monad.Catch (MonadThrow (..)) import Control.Monad.IO.Class import Control.Monad.Trans.Resource (MonadResource, ResourceT, runResourceT) import Data.Aeson import Data.Aeson.Lens import Data.ByteString (ByteString) import Data.Coerce import qualified Data.Conduit as C import qualified Data.Conduit.Attoparsec as CA import qualified Data.Conduit.List as CL import qualified Data.Map as M import qualified Data.Text.Encoding as T import Network.HTTP.Client.MultipartFormData import qualified Network.HTTP.Conduit as HTTP import qualified Network.HTTP.Types as HT import Web.Authenticate.OAuth (signOAuth) #if __GLASGOW_HASKELL__ < 804 import Data.Monoid #endif makeRequest :: APIRequest apiName responseType -> IO HTTP.Request makeRequest (APIRequest m u pa) = makeRequest' m u (makeSimpleQuery pa) makeRequest (APIRequestMultipart m u param prt) = formDataBody body =<< makeRequest' m u [] where body = prt ++ partParam partParam = Prelude.map (uncurry partBS . over _1 T.decodeUtf8) (makeSimpleQuery param) makeRequest (APIRequestJSON m u param body) = do req <- makeRequest' m u (makeSimpleQuery param) return $ req { HTTP.requestBody = HTTP.RequestBodyLBS $ encode body , HTTP.requestHeaders = ("Content-Type", "application/json") : HTTP.requestHeaders req } makeRequest' :: HT.Method -- ^ HTTP request method (GET or POST) -> String -- ^ API Resource URL -> HT.SimpleQuery -- ^ Query -> IO HTTP.Request makeRequest' m url query = do req <- HTTP.parseRequest url let addParams = if m == "POST" then HTTP.urlEncodedBody query else \r -> r { HTTP.queryString = HT.renderSimpleQuery False query } return $ addParams $ req { HTTP.method = m } class ResponseBodyType a where parseResponseBody :: Response (C.ConduitM () ByteString (ResourceT IO) ()) -> ResourceT IO (Response a) type NoContent = () instance ResponseBodyType NoContent where parseResponseBody res = case responseStatus res of st | st == HT.status204 -> return $ void res _ -> do body <- C.runConduit $ responseBody res C..| sinkJSON throwM $ TwitterStatusError (responseStatus res) (responseHeaders res) body instance {-# OVERLAPPABLE #-} FromJSON a => ResponseBodyType a where parseResponseBody = getValueOrThrow getResponse :: MonadResource m => TWInfo -> HTTP.Manager -> HTTP.Request -> m (Response (C.ConduitM () ByteString m ())) getResponse TWInfo{..} mgr req = do signedReq <- signOAuth (twOAuth twToken) (twCredential twToken) $ req { HTTP.proxy = twProxy } res <- HTTP.http signedReq mgr return Response { responseStatus = HTTP.responseStatus res , responseHeaders = HTTP.responseHeaders res , responseBody = HTTP.responseBody res } endpoint :: String endpoint = "https://api.twitter.com/1.1/" getValue :: Response (C.ConduitM () ByteString (ResourceT IO) ()) -> ResourceT IO (Response Value) getValue res = do value <- C.runConduit $ responseBody res C..| sinkJSON return $ res { responseBody = value } checkResponse :: Response Value -> Either TwitterError Value checkResponse Response{..} = case responseBody ^? key "errors" of Just errs@(Array _) -> case fromJSON errs of Success errList -> Left $ TwitterErrorResponse responseStatus responseHeaders errList Error msg -> Left $ FromJSONError msg Just err -> Left $ TwitterUnknownErrorResponse responseStatus responseHeaders err Nothing -> if sci < 200 || sci > 400 then Left $ TwitterStatusError responseStatus responseHeaders responseBody else Right responseBody where sci = HT.statusCode responseStatus getValueOrThrow :: FromJSON a => Response (C.ConduitM () ByteString (ResourceT IO) ()) -> ResourceT IO (Response a) getValueOrThrow res = do res' <- getValue res case checkResponse res' of Left err -> throwM err Right _ -> return () case fromJSON (responseBody res') of Success r -> return $ res' { responseBody = r } Error err -> throwM $ FromJSONError err -- | Perform an 'APIRequest' and then provide the response which is mapped to a suitable type of -- . -- -- Example: -- -- @ -- user <- 'call' twInfo mgr $ 'accountVerifyCredentials' -- print user -- @ -- -- If you need raw JSON value which is parsed by , -- use 'call'' to obtain it. call :: ResponseBodyType responseType => TWInfo -- ^ Twitter Setting -> HTTP.Manager -> APIRequest apiName responseType -> IO responseType call = call' -- | Perform an 'APIRequest' and then provide the response. -- The response of this function is not restrict to @responseType@, -- so you can choose an arbitrarily type of FromJSON instances. call' :: ResponseBodyType value => TWInfo -- ^ Twitter Setting -> HTTP.Manager -> APIRequest apiName responseType -> IO value call' info mgr req = responseBody `fmap` callWithResponse' info mgr req -- | Perform an 'APIRequest' and then provide the 'Response'. -- -- Example: -- -- @ -- res \<- 'callWithResponse' twInfo mgr $ 'accountVerifyCredentials' -- 'print' $ 'responseStatus' res -- 'print' $ 'responseHeaders' res -- 'print' $ 'responseBody' res -- @ callWithResponse :: ResponseBodyType responseType => TWInfo -- ^ Twitter Setting -> HTTP.Manager -> APIRequest apiName responseType -> IO (Response responseType) callWithResponse = callWithResponse' -- | Perform an 'APIRequest' and then provide the 'Response'. -- The response of this function is not restrict to @responseType@, -- so you can choose an arbitrarily type of FromJSON instances. -- -- Example: -- -- @ -- res \<- 'callWithResponse'' twInfo mgr $ 'accountVerifyCredentials' -- 'print' $ 'responseStatus' res -- 'print' $ 'responseHeaders' res -- 'print' $ 'responseBody' (res :: Value) -- @ callWithResponse' :: ResponseBodyType value => TWInfo -> HTTP.Manager -> APIRequest apiName responseType -> IO (Response value) callWithResponse' info mgr req = runResourceT $ do res <- getResponse info mgr =<< liftIO (makeRequest req) parseResponseBody res -- | A wrapper function to perform multiple API request with changing @max_id@ parameter. -- -- This function cooperate with instances of 'HasMaxIdParam'. sourceWithMaxId :: ( MonadIO m , FromJSON responseType , AsStatus responseType , HasParam "max_id" Integer supports ) => TWInfo -- ^ Twitter Setting -> HTTP.Manager -> APIRequest supports [responseType] -> C.ConduitT () responseType m () sourceWithMaxId info mgr = loop where loop req = do res <- liftIO $ call info mgr req case getMinId res of Just mid -> do CL.sourceList res loop $ req & #max_id ?~ mid - 1 Nothing -> CL.sourceList res getMinId = minimumOf (traverse . status_id) -- | A wrapper function to perform multiple API request with changing @max_id@ parameter. -- The response of this function is not restrict to @responseType@, -- so you can choose an arbitrarily type of FromJSON instances. -- -- This function cooperate with instances of 'HasMaxIdParam'. sourceWithMaxId' :: ( MonadIO m , HasParam "max_id" Integer supports ) => TWInfo -- ^ Twitter Setting -> HTTP.Manager -> APIRequest supports [responseType] -> C.ConduitT () Value m () sourceWithMaxId' info mgr = loop where loop req = do (res :: [Value]) <- liftIO $ call' info mgr req case minimumOf (traverse . key "id" . _Integer) res of Just mid -> do CL.sourceList res loop $ req & #max_id ?~ mid - 1 Nothing -> CL.sourceList res -- | A wrapper function to perform multiple API request with changing @cursor@ parameter. -- -- This function cooperate with instances of 'HasCursorParam'. sourceWithCursor :: ( MonadIO m , FromJSON responseType , CursorKey ck , HasParam "cursor" Integer supports ) => TWInfo -- ^ Twitter Setting -> HTTP.Manager -> APIRequest supports (WithCursor Integer ck responseType) -> C.ConduitT () responseType m () sourceWithCursor info mgr req = loop (Just (-1)) where loop Nothing = CL.sourceNull loop (Just 0) = CL.sourceNull loop (Just cur) = do res <- liftIO $ call info mgr $ req & #cursor ?~ cur CL.sourceList $ contents res loop $ nextCursor res -- | A wrapper function to perform multiple API request with changing @cursor@ parameter. -- The response of this function is not restrict to @responseType@, -- so you can choose an arbitrarily type of FromJSON instances. -- -- This function cooperate with instances of 'HasCursorParam'. sourceWithCursor' :: ( MonadIO m , CursorKey ck , HasParam "cursor" Integer supports ) => TWInfo -- ^ Twitter Setting -> HTTP.Manager -> APIRequest supports (WithCursor Integer ck responseType) -> C.ConduitT () Value m () sourceWithCursor' info mgr req = loop (Just (-1)) where relax :: APIRequest apiName (WithCursor Integer ck responseType) -> APIRequest apiName (WithCursor Integer ck Value) relax = coerce loop Nothing = CL.sourceNull loop (Just 0) = CL.sourceNull loop (Just cur) = do res <- liftIO $ call info mgr $ relax $ req & #cursor ?~ cur CL.sourceList $ contents res loop $ nextCursor res -- | A wrapper function to perform multiple API request with @SearchResult@. sourceWithSearchResult :: ( MonadIO m , FromJSON responseType ) => TWInfo -- ^ Twitter Setting -> HTTP.Manager -> APIRequest supports (SearchResult [responseType]) -> m (SearchResult (C.ConduitT () responseType m ())) sourceWithSearchResult info mgr req = do res <- liftIO $ call info mgr req let body = CL.sourceList (res ^. searchResultStatuses) <> loop (res ^. searchResultSearchMetadata . searchMetadataNextResults) return $ res & searchResultStatuses .~ body where origQueryMap = req ^. params . to M.fromList loop Nothing = CL.sourceNull loop (Just nextResultsStr) = do let nextResults = nextResultsStr & HT.parseSimpleQuery . T.encodeUtf8 & traversed . _2 %~ (PVString . T.decodeUtf8) nextParams = M.toList $ M.union (M.fromList nextResults) origQueryMap res <- liftIO $ call info mgr $ req & params .~ nextParams CL.sourceList (res ^. searchResultStatuses) loop $ res ^. searchResultSearchMetadata . searchMetadataNextResults -- | A wrapper function to perform multiple API request with @SearchResult@. sourceWithSearchResult' :: ( MonadIO m ) => TWInfo -- ^ Twitter Setting -> HTTP.Manager -> APIRequest supports (SearchResult [responseType]) -> m (SearchResult (C.ConduitT () Value m ())) sourceWithSearchResult' info mgr req = do res <- liftIO $ call info mgr $ relax req let body = CL.sourceList (res ^. searchResultStatuses) <> loop (res ^. searchResultSearchMetadata . searchMetadataNextResults) return $ res & searchResultStatuses .~ body where origQueryMap = req ^. params . to M.fromList relax :: APIRequest apiName (SearchResult [responseType]) -> APIRequest apiName (SearchResult [Value]) relax = coerce loop Nothing = CL.sourceNull loop (Just nextResultsStr) = do let nextResults = nextResultsStr & HT.parseSimpleQuery . T.encodeUtf8 & traversed . _2 %~ (PVString . T.decodeUtf8) nextParams = M.toList $ M.union (M.fromList nextResults) origQueryMap res <- liftIO $ call info mgr $ relax $ req & params .~ nextParams CL.sourceList (res ^. searchResultStatuses) loop $ res ^. searchResultSearchMetadata . searchMetadataNextResults sinkJSON :: ( MonadThrow m ) => C.ConduitT ByteString o m Value sinkJSON = CA.sinkParser json sinkFromJSON :: ( FromJSON a , MonadThrow m ) => C.ConduitT ByteString o m a sinkFromJSON = do v <- sinkJSON case fromJSON v of Error err -> throwM $ FromJSONError err Success r -> return r twitter-conduit-0.5.0/Web/Twitter/Conduit/Request.hs0000644000000000000000000000624613652033050020624 0ustar0000000000000000{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE TypeFamilies #-} module Web.Twitter.Conduit.Request ( HasParam , APIRequest(..) ) where import Data.Aeson import GHC.TypeLits (Symbol) import Network.HTTP.Client.MultipartFormData import qualified Network.HTTP.Types as HT import Web.Twitter.Conduit.Request.Internal -- $setup -- >>> :set -XOverloadedStrings -XDataKinds -XTypeOperators -- >>> import Control.Lens -- >>> import Web.Twitter.Conduit.Parameters -- >>> type SampleId = Integer -- >>> type SampleApi = '["count" ':= Integer, "max_id" ':= Integer] -- >>> let sampleApiRequest :: APIRequest SampleApi [SampleId]; sampleApiRequest = APIRequest "GET" "https://api.twitter.com/sample/api.json" [] -- | 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@. -- -- @ -- type SampleId = 'Integer' -- sampleApiRequest :: 'APIRequest' SampleApi [SampleId] -- sampleApiRequest = 'APIRequest' \"GET\" \"https:\/\/api.twitter.com\/sample\/api.json\" [] -- type SampleApi = '[ "count" ':= Integer -- , "max_id" ':= Integer -- ] -- -- @ -- -- We can obtain request params from @'APIRequest' SampleApi [SampleId]@ : -- -- >>> sampleApiRequest ^. params -- [] -- -- The second type parameter of the APIRequest represents the allowed parameters for the APIRequest. -- For example, @sampleApiRequest@ has 2 @Integer@ parameters, that is "count" and "max_id". -- You can update those parameters by label lenses (@#count@ and @#max_id@ respectively) -- -- >>> (sampleApiRequest & #count ?~ 100 & #max_id ?~ 1234567890) ^. params -- [("max_id",PVInteger {unPVInteger = 1234567890}),("count",PVInteger {unPVInteger = 100})] -- >>> (sampleApiRequest & #count ?~ 100 & #max_id ?~ 1234567890 & #count .~ Nothing) ^. params -- [("max_id",PVInteger {unPVInteger = 1234567890})] data APIRequest (supports :: [Param Symbol *]) responseType = APIRequest { _method :: HT.Method , _url :: String , _params :: APIQuery } | APIRequestMultipart { _method :: HT.Method , _url :: String , _params :: APIQuery , _part :: [Part] } | APIRequestJSON { _method :: HT.Method , _url :: String , _params :: APIQuery , _body :: Value } instance Parameters (APIRequest supports responseType) where type SupportParameters (APIRequest supports responseType) = supports params f (APIRequest m u pa) = APIRequest m u <$> f pa params f (APIRequestMultipart m u pa prt) = (\p -> APIRequestMultipart m u p prt) <$> f pa params f (APIRequestJSON m u pa body) = (\p -> APIRequestJSON m u p body) <$> f pa instance Show (APIRequest apiName responseType) where show (APIRequest m u p) = "APIRequest " ++ show m ++ " " ++ show u ++ " " ++ show (makeSimpleQuery p) show (APIRequestMultipart m u p _) = "APIRequestMultipart " ++ show m ++ " " ++ show u ++ " " ++ show (makeSimpleQuery p) show (APIRequestJSON m u p _) = "APIRequestJSON " ++ show m ++ " " ++ show u ++ " " ++ show (makeSimpleQuery p) twitter-conduit-0.5.0/Web/Twitter/Conduit/Request/Internal.hs0000644000000000000000000000671213652033050022376 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Web.Twitter.Conduit.Request.Internal where import Control.Lens import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as S8 import Data.Proxy import Data.Text (Text) import qualified Data.Text.Encoding as T import Data.Time.Calendar (Day) import GHC.OverloadedLabels import GHC.TypeLits import qualified Network.HTTP.Types as HT data Param label t = label := t type EmptyParams = ('[] :: [Param Symbol *]) type HasParam (label :: Symbol) (paramType :: *) (params :: [Param Symbol *]) = ParamType label params ~ paramType type family ParamType (label :: Symbol) (params :: [Param Symbol *]) :: * where ParamType label ((label ':= paramType) ': ks) = paramType ParamType label ((label' ':= paramType') ': ks) = ParamType label ks type APIQuery = [APIQueryItem] type APIQueryItem = (ByteString, PV) data PV = PVInteger { unPVInteger :: Integer } | PVBool { unPVBool :: Bool } | PVString { unPVString :: Text } | PVIntegerArray { unPVIntegerArray :: [Integer] } | PVStringArray { unPVStringArray :: [Text] } | PVDay { unPVDay :: Day } deriving (Show, Eq) class Parameters req where type SupportParameters req :: [Param Symbol *] params :: Lens' req APIQuery class ParameterValue a where wrap :: a -> PV unwrap :: PV -> a instance ParameterValue Integer where wrap = PVInteger unwrap = unPVInteger instance ParameterValue Bool where wrap = PVBool unwrap = unPVBool instance ParameterValue Text where wrap = PVString unwrap = unPVString instance ParameterValue [Integer] where wrap = PVIntegerArray unwrap = unPVIntegerArray instance ParameterValue [Text] where wrap = PVStringArray unwrap = unPVStringArray instance ParameterValue Day where wrap = PVDay unwrap = unPVDay makeSimpleQuery :: APIQuery -> HT.SimpleQuery makeSimpleQuery = traversed . _2 %~ paramValueBS paramValueBS :: PV -> ByteString paramValueBS (PVInteger i) = S8.pack . show $ i paramValueBS (PVBool True) = "true" paramValueBS (PVBool False) = "false" paramValueBS (PVString txt) = T.encodeUtf8 txt paramValueBS (PVIntegerArray iarr) = S8.intercalate "," $ map (S8.pack . show) iarr paramValueBS (PVStringArray iarr) = S8.intercalate "," $ map T.encodeUtf8 iarr paramValueBS (PVDay day) = S8.pack . show $ day rawParam :: (Parameters p, ParameterValue a) => ByteString -- ^ key -> Lens' p (Maybe a) rawParam key = lens getter setter where getter = preview $ params . to (lookup key) . _Just . to unwrap setter = flip (over params . replace key) replace k (Just v) = ((k, wrap v):) . dropAssoc k replace k Nothing = dropAssoc k dropAssoc k = filter ((/= k) . fst) instance ( Parameters req , ParameterValue a , KnownSymbol label , HasParam label a (SupportParameters req) , Functor f , lens ~ ((Maybe a -> f (Maybe a)) -> req -> f req)) => IsLabel label lens where #if MIN_VERSION_base(4, 10, 0) fromLabel = rawParam key #else fromLabel _ = rawParam key #endif where key = S8.pack (symbolVal (Proxy :: Proxy label)) twitter-conduit-0.5.0/Web/Twitter/Conduit/Response.hs0000644000000000000000000000353213652033050020765 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE OverloadedStrings #-} module Web.Twitter.Conduit.Response ( Response (..) , TwitterError (..) , TwitterErrorMessage (..) ) where import Control.Exception import Data.Aeson import Data.Data import qualified Data.Text as T import Network.HTTP.Types (Status, ResponseHeaders, Status, ResponseHeaders) data Response responseType = Response { responseStatus :: Status , responseHeaders :: ResponseHeaders , responseBody :: responseType } deriving (Show, Eq, Typeable, Functor, Foldable, Traversable) data TwitterError = FromJSONError String | TwitterErrorResponse Status ResponseHeaders [TwitterErrorMessage] | TwitterUnknownErrorResponse Status ResponseHeaders Value | TwitterStatusError Status ResponseHeaders Value deriving (Show, Typeable, Eq) instance Exception TwitterError -- | Twitter Error Messages -- -- see detail: data TwitterErrorMessage = TwitterErrorMessage { twitterErrorCode :: Int , twitterErrorMessage :: T.Text } deriving (Show, Data, Typeable) instance Eq TwitterErrorMessage where TwitterErrorMessage { twitterErrorCode = a } == TwitterErrorMessage { twitterErrorCode = b } = a == b instance Ord TwitterErrorMessage where compare TwitterErrorMessage { twitterErrorCode = a } TwitterErrorMessage { twitterErrorCode = b } = a `compare` b instance Enum TwitterErrorMessage where fromEnum = twitterErrorCode toEnum a = TwitterErrorMessage a T.empty instance FromJSON TwitterErrorMessage where parseJSON (Object o) = TwitterErrorMessage <$> o .: "code" <*> o .: "message" parseJSON v = fail $ "unexpected: " ++ show v twitter-conduit-0.5.0/Web/Twitter/Conduit/Cursor.hs0000644000000000000000000000452213652033050020444 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE ScopedTypeVariables #-} module Web.Twitter.Conduit.Cursor ( CursorKey (..) , IdsCursorKey , UsersCursorKey , ListsCursorKey , EventsCursorKey , WithCursor (..) ) where import Data.Aeson import Data.Text (Text) import Web.Twitter.Types (checkError) -- $setup -- >>> type UserId = Integer class CursorKey a where cursorKey :: a -> 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" -- | Phantom type to specify the key which point out the content in the response. data ListsCursorKey instance CursorKey ListsCursorKey where cursorKey = const "lists" data EventsCursorKey instance CursorKey EventsCursorKey where cursorKey = const "events" -- | 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 Integer IdsCursorKey UserId) -- >>> nextCursor res -- Just 1234567890 -- >>> contents res -- [1111111111] -- -- >>> let Just res = decode "{\"previous_cursor\": 0, \"next_cursor\": 0, \"users\": [1000]}" :: Maybe (WithCursor Integer UsersCursorKey UserId) -- >>> nextCursor res -- Just 0 -- >>> contents res -- [1000] -- -- >>> let Just res = decode "{\"next_cursor\": \"hogehoge\", \"events\": [1000]}" :: Maybe (WithCursor Text EventsCursorKey UserId) -- >>> nextCursor res -- Just "hogehoge" -- >>> contents res -- [1000] data WithCursor cursorType cursorKey wrapped = WithCursor { previousCursor :: Maybe cursorType , nextCursor :: Maybe cursorType , contents :: [wrapped] } deriving Show instance (FromJSON wrapped, FromJSON ct, CursorKey c) => FromJSON (WithCursor ct c wrapped) where parseJSON (Object o) = checkError o >> WithCursor <$> o .:? "previous_cursor" <*> o .:? "next_cursor" <*> o .: cursorKey (undefined :: c) parseJSON _ = mempty twitter-conduit-0.5.0/Web/Twitter/Conduit/Parameters.hs0000644000000000000000000000465513652033050021301 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Web.Twitter.Conduit.Parameters ( UserParam(..) , UserListParam(..) , ListParam(..) , MediaData(..) , mkUserParam , mkUserListParam , mkListParam ) where import qualified Data.Text as T import Network.HTTP.Client (RequestBody) import Web.Twitter.Conduit.Request.Internal (APIQuery, PV(..)) import Web.Twitter.Types -- $setup -- >>> import Web.Twitter.Conduit.Request.Internal 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) data MediaData = MediaFromFile FilePath | MediaRequestBody FilePath RequestBody -- | converts 'UserParam' to 'HT.SimpleQuery'. -- -- >>> makeSimpleQuery . mkUserParam $ UserIdParam 123456 -- [("user_id","123456")] -- >>> makeSimpleQuery . mkUserParam $ ScreenNameParam "thimura" -- [("screen_name","thimura")] mkUserParam :: UserParam -> APIQuery mkUserParam (UserIdParam uid) = [("user_id", PVInteger uid)] mkUserParam (ScreenNameParam sn) = [("screen_name", PVString . T.pack $ sn)] -- | converts 'UserListParam' to 'HT.SimpleQuery'. -- -- >>> makeSimpleQuery . mkUserListParam $ UserIdListParam [123456] -- [("user_id","123456")] -- >>> makeSimpleQuery . mkUserListParam $ UserIdListParam [123456, 654321] -- [("user_id","123456,654321")] -- >>> makeSimpleQuery . mkUserListParam $ ScreenNameListParam ["thimura", "NikaidouShinku"] -- [("screen_name","thimura,NikaidouShinku")] mkUserListParam :: UserListParam -> APIQuery mkUserListParam (UserIdListParam uids) = [("user_id", PVIntegerArray uids)] mkUserListParam (ScreenNameListParam sns) = [("screen_name", PVStringArray (map T.pack sns))] -- | converts 'ListParam' to 'HT.SimpleQuery'. -- -- >>> makeSimpleQuery . mkListParam $ ListIdParam 123123 -- [("list_id","123123")] -- >>> makeSimpleQuery . mkListParam $ ListNameParam "thimura/haskell" -- [("slug","haskell"),("owner_screen_name","thimura")] mkListParam :: ListParam -> APIQuery mkListParam (ListIdParam lid) = [("list_id", PVInteger lid)] mkListParam (ListNameParam listname) = [("slug", PVString (T.pack lstName)), ("owner_screen_name", PVString (T.pack screenName))] where (screenName, ln) = span (/= '/') listname lstName = drop 1 ln twitter-conduit-0.5.0/Web/Twitter/Conduit/ParametersDeprecated.hs0000644000000000000000000001176713652033050023264 0ustar0000000000000000{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} module Web.Twitter.Conduit.ParametersDeprecated where import Data.Text (Text) import Web.Twitter.Types import Data.Time.Calendar (Day) import Web.Twitter.Conduit.Request.Internal import Control.Lens count :: (Parameters p, HasParam "count" Integer (SupportParameters p)) => Lens' p (Maybe Integer) count = rawParam "count" sinceId :: (Parameters p, HasParam "since_id" Integer (SupportParameters p)) => Lens' p (Maybe Integer) sinceId = rawParam "since_id" maxId :: (Parameters p, HasParam "max_id" Integer (SupportParameters p)) => Lens' p (Maybe Integer) maxId = rawParam "max_id" page :: (Parameters p, HasParam "page" Integer (SupportParameters p)) => Lens' p (Maybe Integer) page = rawParam "page" trimUser :: (Parameters p, HasParam "trim_user" Bool (SupportParameters p)) => Lens' p (Maybe Bool) trimUser = rawParam "trim_user" excludeReplies :: (Parameters p, HasParam "exclude_replies" Bool (SupportParameters p)) => Lens' p (Maybe Bool) excludeReplies = rawParam "exclude_replies" contributorDetails :: (Parameters p, HasParam "contributor_details" Bool (SupportParameters p)) => Lens' p (Maybe Bool) contributorDetails = rawParam "contributor_details" includeEntities :: (Parameters p, HasParam "include_entities" Bool (SupportParameters p)) => Lens' p (Maybe Bool) includeEntities = rawParam "include_entities" includeEmail :: (Parameters p, HasParam "include_email" Bool (SupportParameters p)) => Lens' p (Maybe Bool) includeEmail = rawParam "include_email" includeUserEntities :: (Parameters p, HasParam "include_user_entities" Bool (SupportParameters p)) => Lens' p (Maybe Bool) includeUserEntities = rawParam "include_user_entities" includeRts :: (Parameters p, HasParam "include_rts" Bool (SupportParameters p)) => Lens' p (Maybe Bool) includeRts = rawParam "include_rts" includeMyRetweet :: (Parameters p, HasParam "include_my_retweet" Bool (SupportParameters p)) => Lens' p (Maybe Bool) includeMyRetweet = rawParam "include_my_retweet" includeExtAltText :: (Parameters p, HasParam "include_ext_alt_text" Bool (SupportParameters p)) => Lens' p (Maybe Bool) includeExtAltText = rawParam "include_ext_alt_text" inReplyToStatusId :: (Parameters p, HasParam "in_reply_to_status_id" Integer (SupportParameters p)) => Lens' p (Maybe Integer) inReplyToStatusId = rawParam "in_reply_to_status_id" displayCoordinates :: (Parameters p, HasParam "display_coordinates" Bool (SupportParameters p)) => Lens' p (Maybe Bool) displayCoordinates = rawParam "display_coordinates" possiblySensitive :: (Parameters p, HasParam "possibly_sensitive" Bool (SupportParameters p)) => Lens' p (Maybe Bool) possiblySensitive = rawParam "possibly_sensitive" lang :: (Parameters p, HasParam "lang" Text (SupportParameters p)) => Lens' p (Maybe Text) lang = rawParam "lang" language :: (Parameters p, HasParam "language" Text (SupportParameters p)) => Lens' p (Maybe Text) language = rawParam "language" locale :: (Parameters p, HasParam "locale" Text (SupportParameters p)) => Lens' p (Maybe Text) locale = rawParam "locale" filterLevel :: (Parameters p, HasParam "filter_level" Text (SupportParameters p)) => Lens' p (Maybe Text) filterLevel = rawParam "filter_level" stallWarnings :: (Parameters p, HasParam "stall_warnings" Bool (SupportParameters p)) => Lens' p (Maybe Bool) stallWarnings = rawParam "stall_warnings" replies :: (Parameters p, HasParam "replies" Text (SupportParameters p)) => Lens' p (Maybe Text) replies = rawParam "replies" until :: (Parameters p, HasParam "until" Day (SupportParameters p)) => Lens' p (Maybe Day) until = rawParam "until" skipStatus :: (Parameters p, HasParam "skip_status" Bool (SupportParameters p)) => Lens' p (Maybe Bool) skipStatus = rawParam "skip_status" follow :: (Parameters p, HasParam "follow" Bool (SupportParameters p)) => Lens' p (Maybe Bool) follow = rawParam "follow" map :: (Parameters p, HasParam "map" Bool (SupportParameters p)) => Lens' p (Maybe Bool) map = rawParam "map" mediaIds :: (Parameters p, HasParam "media_ids" [Integer] (SupportParameters p)) => Lens' p (Maybe [Integer]) mediaIds = rawParam "media_ids" description :: (Parameters p, HasParam "description" Text (SupportParameters p)) => Lens' p (Maybe Text) description = rawParam "description" name :: (Parameters p, HasParam "name" Text (SupportParameters p)) => Lens' p (Maybe Text) name = rawParam "name" profileLinkColor :: (Parameters p, HasParam "profile_link_color" Text (SupportParameters p)) => Lens' p (Maybe Text) profileLinkColor = rawParam "profile_link_color" location :: (Parameters p, HasParam "location" Text (SupportParameters p)) => Lens' p (Maybe Text) location = rawParam "location" url :: (Parameters p, HasParam "url" URIString (SupportParameters p)) => Lens' p (Maybe URIString) url = rawParam "url" fullText :: (Parameters p, HasParam "full_text" Bool (SupportParameters p)) => Lens' p (Maybe Bool) fullText = rawParam "full_text" with :: (Parameters p, HasParam "with" Text (SupportParameters p)) => Lens' p (Maybe Text) with = rawParam "with" twitter-conduit-0.5.0/tests/spec_main.hs0000644000000000000000000000006113652033050016475 0ustar0000000000000000import Spec import Test.Hspec main = hspec spec twitter-conduit-0.5.0/tests/Spec.hs0000644000000000000000000000007413652033050015435 0ustar0000000000000000{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --no-main #-} twitter-conduit-0.5.0/tests/ApiSpec.hs0000644000000000000000000000370013652033050016066 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-} module ApiSpec where import Control.Lens import Data.Conduit import qualified Data.Conduit.List as CL import Network.HTTP.Conduit import System.IO.Unsafe import TestUtils import Web.Twitter.Conduit (call, sourceWithCursor, TWInfo) import Web.Twitter.Conduit.Api import Web.Twitter.Conduit.Lens import qualified Web.Twitter.Conduit.Parameters as Param import Web.Twitter.Types.Lens import Test.Hspec twInfo :: TWInfo twInfo = unsafePerformIO getTWInfo mgr :: Manager mgr = unsafePerformIO $ newManager tlsManagerSettings {-# NOINLINE mgr #-} spec :: Spec spec = do unit #ifdef RUN_INTEGRATED_TEST integrated #endif unit :: Spec unit = return () integrated :: Spec integrated = do describe "friendsIds" $ do it "returns a cursored collection of users IDs" $ do res <- call twInfo mgr $ friendsIds (Param.ScreenNameParam "thimura") res ^. contents . to length `shouldSatisfy` (> 0) it "iterate with sourceWithCursor" $ do let src = sourceWithCursor twInfo mgr $ friendsIds (Param.ScreenNameParam "thimura") friends <- src $$ CL.consume length friends `shouldSatisfy` (>= 0) describe "listsMembers" $ do it "returns a cursored collection of the member of specified list" $ do res <- call twInfo mgr $ listsMembers (Param.ListNameParam "thimura/haskell") res ^. contents . to length `shouldSatisfy` (>= 0) it "should raise error when specified list does not exists" $ do let action = call twInfo mgr $ listsMembers (Param.ListNameParam "thimura/haskell_ne") action `shouldThrow` anyException it "iterate with sourceWithCursor" $ do let src = sourceWithCursor twInfo mgr $ listsMembers (Param.ListNameParam "thimura/haskell") members <- src $$ CL.consume members ^.. traversed . userScreenName `shouldContain` ["Hackage"] twitter-conduit-0.5.0/tests/BaseSpec.hs0000644000000000000000000000715313652033050016235 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module BaseSpec where import Web.Twitter.Conduit.Response import Web.Twitter.Conduit.Base import Control.Applicative import Control.Lens import Data.Aeson import Data.Aeson.Lens import Data.Conduit import qualified Data.Conduit.Attoparsec as CA import Data.Maybe import qualified Data.Text as T import qualified Network.HTTP.Types as HT import Test.Hspec spec :: Spec spec = do unit unit :: Spec unit = do describe "checkResponse" $ do describe "when the response has \"errors\" key" $ do let errorMessage = fromJust . decode $ "{\"errors\":[{\"message\":\"Sorry, that page does not exist\",\"code\":34}]}" response = Response HT.status404 [] errorMessage result = checkResponse response it "returns TwitterErrorResponse" $ do case result of Left res@(TwitterErrorResponse _ _ msgs) -> do res `shouldBe` TwitterErrorResponse HT.status404 [] [TwitterErrorMessage 34 ""] twitterErrorMessage (head msgs) `shouldBe` "Sorry, that page does not exist" _ -> expectationFailure $ "Unexpected " ++ show result describe "when the response does not has \"errors\" key but have error status code" $ do let errorMessage = fromJust . decode $ "{}" response = Response HT.status404 [] errorMessage result = checkResponse response it "returns TwitterStatusError" $ do case result of Left (TwitterStatusError st hdr body) -> do st `shouldBe` HT.status404 hdr `shouldBe` [] body `shouldBe` errorMessage _ -> expectationFailure $ "Unexpected " ++ show result describe "sinkJSON" $ do describe "when valid JSON input" $ do let input = "{\"test\": \"input\", \"status\": 200 }" it "can consume the input from Source and returns JSON Value" $ do res <- yield input $$ sinkJSON res ^. key "test" . _String `shouldBe` "input" res ^? key "status" . _Integer `shouldBe` Just 200 describe "when invalid JSON input" $ do let input = "{]" it "should raise Data.Conduit.Attoparsec.ParseError" $ do let parseErrorException (CA.ParseError {}) = True parseErrorException _ = False action = yield input $$ sinkJSON action `shouldThrow` parseErrorException describe "sinkFromJSON" $ do describe "when valid JSON input" $ do let input = "{\"test\": \"input\", \"status\": 200 }" it "can consume the input from Source and returns a value which type is the specified one" $ do res <- yield input $$ sinkFromJSON res `shouldBe` TestJSON "input" 200 describe "when the JSON value does not have expected format" $ do let input = "{\"status\": 200}" it "should raise FromJSONError" $ do let fromJSONException (FromJSONError {}) = True fromJSONException _ = False action :: IO TestJSON action = yield input $$ sinkFromJSON action `shouldThrow` fromJSONException data TestJSON = TestJSON { testField :: T.Text , testStatus :: Int } deriving (Show, Eq) instance FromJSON TestJSON where parseJSON (Object o) = TestJSON <$> o .: "test" <*> o .: "status" parseJSON v = fail $ "Unexpected: " ++ show v twitter-conduit-0.5.0/tests/StatusSpec.hs0000644000000000000000000000765613652033050016656 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE CPP #-} module StatusSpec where import Control.Lens import Data.Conduit import qualified Data.Conduit.List as CL import Data.Time import Network.HTTP.Conduit import System.IO.Unsafe import Web.Twitter.Conduit (call, accountVerifyCredentials, sourceWithMaxId, TWInfo) import qualified Web.Twitter.Conduit.Parameters as Param import Web.Twitter.Conduit.Status as Status import Web.Twitter.Types.Lens import TestUtils import Test.Hspec twInfo :: TWInfo twInfo = unsafePerformIO getTWInfo mgr :: Manager mgr = unsafePerformIO $ newManager tlsManagerSettings {-# NOINLINE mgr #-} self :: User self = unsafePerformIO $ call twInfo mgr $ accountVerifyCredentials {-# NOINLINE self #-} spec :: Spec spec = do unit #ifdef RUN_INTEGRATED_TEST integrated #endif unit :: Spec unit = return () integrated :: Spec integrated = do describe "mentionsTimeline" $ do it "returns the 20 most resent mentions for user" $ do res <- call twInfo mgr mentionsTimeline length res `shouldSatisfy` (> 0) let mentionsScreenName = res ^.. traversed . statusEntities . _Just . enUserMentions . traversed . entityBody . userEntityUserScreenName mentionsScreenName `shouldSatisfy` allOf folded (== (self ^. userScreenName)) length mentionsScreenName `shouldSatisfy` (== length res) describe "userTimeline" $ do it "returns the 20 most recent tweets posted by the user indicated by ScreenNameParam" $ do res <- call twInfo mgr $ userTimeline (Param.ScreenNameParam "thimura") length res `shouldSatisfy` (== 20) res `shouldSatisfy` (allOf folded (^. statusUser . userScreenName . to (== "thimura"))) it "returns the recent tweets which include RTs when specified include_rts option" $ do res <- call twInfo mgr $ userTimeline (Param.ScreenNameParam "thimura") & #count ?~ 100 & #include_rts ?~ True res `shouldSatisfy` (anyOf (folded . statusRetweetedStatus . _Just . statusUser . userScreenName) (/= "thimura")) it "iterate with sourceWithMaxId" $ do let src = sourceWithMaxId twInfo mgr $ userTimeline (Param.ScreenNameParam "thimura") & #count ?~ 200 tl <- src $$ CL.isolate 600 =$ CL.consume length tl `shouldSatisfy` (== 600) let ids = tl ^.. traversed . statusId zip ids (tail ids) `shouldSatisfy` all (\(a, b) -> a > b) describe "homeTimeline" $ do it "returns the most recent tweets in home timeline" $ do res <- call twInfo mgr homeTimeline length res `shouldSatisfy` (> 0) describe "showId" $ do it "works for the known tweets" $ do res <- call twInfo mgr $ showId 477833886768959488 res ^. statusId `shouldBe` 477833886768959488 res ^. statusText `shouldBe` "真紅かわいいはアレセイア" res ^. statusCreatedAt `shouldBe` UTCTime (fromGregorian 2014 6 14) (secondsToDiffTime 55450) res ^. statusUser . userScreenName `shouldBe` "thimura" describe "update & destroyId" $ do it "posts new tweet and destroy it" $ do res1 <- call twInfo mgr $ update "おまえの明日が、今日よりもずっと、楽しい事で溢れているようにと、祈っているよ" res1 ^. statusUser . userScreenName `shouldBe` self ^. userScreenName res2 <- call twInfo mgr $ destroyId (res1 ^. statusId) res2 ^. statusId `shouldBe` res1 ^. statusId describe "lookup" $ do it "works for the known tweets" $ do res <- call twInfo mgr $ Status.lookup [438691466345340928, 477757405942411265] length res `shouldSatisfy` (== 2) (res !! 0) ^. statusId `shouldBe` 438691466345340928 (res !! 1) ^. statusId `shouldBe` 477757405942411265 twitter-conduit-0.5.0/tests/TestUtils.hs0000644000000000000000000000156613652033050016512 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module TestUtils (getTWInfo) where import qualified Data.ByteString.Char8 as S8 import System.Environment import Web.Twitter.Conduit getOAuthTokens :: IO (OAuth, Credential) getOAuthTokens = do consumerKey <- getEnv' "OAUTH_CONSUMER_KEY" consumerSecret <- getEnv' "OAUTH_CONSUMER_SECRET" accessToken <- getEnv' "OAUTH_ACCESS_TOKEN" accessSecret <- getEnv' "OAUTH_ACCESS_SECRET" let oauth = twitterOAuth { oauthConsumerKey = consumerKey , oauthConsumerSecret = consumerSecret } cred = Credential [ ("oauth_token", accessToken) , ("oauth_token_secret", accessSecret) ] return (oauth, cred) where getEnv' = (S8.pack <$>) . getEnv getTWInfo :: IO TWInfo getTWInfo = do (oa, cred) <- getOAuthTokens return $ setCredential oa cred def twitter-conduit-0.5.0/tests/doctests.hs0000644000000000000000000000043513652033050016374 0ustar0000000000000000module Main where import Build_doctests (flags, pkgs, module_sources) import Data.Foldable (traverse_) import Test.DocTest (doctest) main :: IO () main = do traverse_ putStrLn args -- optionally print arguments doctest args where args = flags ++ pkgs ++ module_sources twitter-conduit-0.5.0/tests/hlint.hs0000644000000000000000000000104513652033050015660 0ustar0000000000000000module Main where import Control.Monad import Data.Maybe import Language.Haskell.HLint import System.Environment import System.Exit main :: IO () main = do args <- getArgs cabalMacros <- getCabalMacrosPath hints <- hlint $ ["Web", "--cpp-define=HLINT", "--cpp-ansi", "--cpp-file=" ++ cabalMacros] ++ args unless (null hints) exitFailure getCabalMacrosPath :: IO FilePath getCabalMacrosPath = do env <- getEnvironment let dist = fromMaybe "dist" $ lookup "HASKELL_DIST_DIR" env return $ dist ++ "/build/autogen/cabal_macros.h" twitter-conduit-0.5.0/LICENSE0000644000000000000000000000246013652033050014053 0ustar0000000000000000Copyright (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. twitter-conduit-0.5.0/Setup.hs0000644000000000000000000000114713652033050014503 0ustar0000000000000000{-# LANGUAGE CPP #-} module Main (main) where #ifndef MIN_VERSION_cabal_doctest #define MIN_VERSION_cabal_doctest(x,y,z) 0 #endif #if MIN_VERSION_cabal_doctest(1,0,0) import Distribution.Extra.Doctest ( defaultMainWithDoctests ) main :: IO () main = defaultMainWithDoctests "doctests" #else #ifdef MIN_VERSION_Cabal -- If the macro is defined, we have new cabal-install, -- but for some reason we don't have cabal-doctest in package-db -- -- Probably we are running cabal sdist, when otherwise using new-build -- workflow import Warning () #endif import Distribution.Simple main :: IO () main = defaultMain #endif twitter-conduit-0.5.0/twitter-conduit.cabal0000644000000000000000000000726313652033050017205 0ustar0000000000000000name: twitter-conduit version: 0.5.0 license: BSD3 license-file: LICENSE author: HATTORI Hiroki, Hideyuki Tanaka, Takahiro HIMURA maintainer: Takahiro HIMURA synopsis: Twitter API package with conduit interface and Streaming API support. category: Web, Conduit stability: Experimental cabal-version: >= 1.10 build-type: Custom homepage: https://github.com/himura/twitter-conduit tested-with: GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5 description: This package provides bindings to Twitter's APIs (see ). . This package uses the http-conduit package for accessing the Twitter API (see ). This package also depends on the twitter-types package (see ). . You can find basic examples in the 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. extra-source-files: .gitignore .travis.yml README.md ChangeLog.md Warning.hs sample/LICENSE sample/twitter-conduit-sample.cabal sample/*.hs sample/common/*.hs tests/*.hs source-repository head type: git location: git://github.com/himura/twitter-conduit.git flag run-integrated-test description: use debug output when running testsuites default: False library ghc-options: -Wall build-depends: base >= 4.9 && < 5 , aeson >= 0.7.0.5 , attoparsec >= 0.10 , authenticate-oauth >= 1.3 , bytestring >= 0.10.2 , conduit >= 1.3 , conduit-extra >= 1.3 , containers , data-default >= 0.3 , exceptions >= 0.5 , ghc-prim , http-client >= 0.5.0 , http-conduit >= 2.3 && < 2.4 , http-types , lens >= 4.4 , lens-aeson >= 1 , resourcet >= 1.0 , text >= 0.11 , time , transformers >= 0.2.2 , twitter-types >= 0.9 , twitter-types-lens >= 0.9 exposed-modules: Web.Twitter.Conduit Web.Twitter.Conduit.Lens Web.Twitter.Conduit.Types Web.Twitter.Conduit.Api Web.Twitter.Conduit.Stream Web.Twitter.Conduit.Status Web.Twitter.Conduit.Base Web.Twitter.Conduit.Request Web.Twitter.Conduit.Request.Internal Web.Twitter.Conduit.Response Web.Twitter.Conduit.Cursor Web.Twitter.Conduit.Parameters Web.Twitter.Conduit.ParametersDeprecated default-language: Haskell2010 test-suite hlint type: exitcode-stdio-1.0 main-is: hlint.hs hs-source-dirs: tests build-depends: base , hlint >= 1.7 default-language: Haskell2010 test-suite doctests type: exitcode-stdio-1.0 main-is: doctests.hs hs-source-dirs: tests build-depends: base , doctest default-language: Haskell2010 test-suite spec_main type: exitcode-stdio-1.0 main-is: spec_main.hs hs-source-dirs: tests if flag(run-integrated-test) CPP-Options: -DRUN_INTEGRATED_TEST build-tool-depends: hspec-discover:hspec-discover >= 2.3.0 build-depends: base , aeson , attoparsec , authenticate-oauth , bytestring , conduit , conduit-extra , containers , data-default , hspec , http-client , http-conduit , http-types , lens , lens-aeson , resourcet , text , time , twitter-conduit , twitter-types , twitter-types-lens other-modules: Spec ApiSpec BaseSpec StatusSpec TestUtils default-language: Haskell2010 custom-setup setup-depends: base , Cabal >= 1.24 , cabal-doctest >= 1 && < 1.1 twitter-conduit-0.5.0/.gitignore0000644000000000000000000000035713652033050015041 0ustar0000000000000000# General \#*# .*~ *~ .#* *.swp .DS_Store .gdb_history TAGS # Object files *.a *.o *.so *.hi *.p_hi a.out # autotool autom4te.cache stamp-h1 # misc *.sqlite Main dist/ cabal-dev/ cabal.sandbox.config .cabal-sandbox .stack-work/ stack.yaml twitter-conduit-0.5.0/.travis.yml0000644000000000000000000001603313652033050015160 0ustar0000000000000000# This Travis job script has been generated by a script via # # haskell-ci 'twitter-conduit.cabal' '-o' '.travis.yml' # # For more information, see https://github.com/haskell-CI/haskell-ci # # version: 0.3.20190521 # language: c dist: xenial git: # whether to recursively clone submodules submodules: false cache: directories: - $HOME/.cabal/packages - $HOME/.cabal/store before_cache: - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log # remove files that are regenerated by 'cabal update' - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.* - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx - rm -rfv $CABALHOME/packages/head.hackage matrix: include: - compiler: ghc-8.6.5 addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-2.4"]}} - compiler: ghc-8.4.4 addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-2.4"]}} - compiler: ghc-8.2.2 addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-2.4"]}} - compiler: ghc-8.0.2 addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.2","cabal-install-2.4"]}} before_install: - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//') - HCPKG="$HC-pkg" - unset CC - CABAL=/opt/ghc/bin/cabal - CABALHOME=$HOME/.cabal - export PATH="$CABALHOME/bin:$PATH" - TOP=$(pwd) - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') )) - echo $HCNUMVER - CABAL="$CABAL -vnormal+nowrap+markoutput" - set -o pipefail - | echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }' >> .colorful.awk echo 'BEGIN { state = "output"; }' >> .colorful.awk echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }' >> .colorful.awk echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }' >> .colorful.awk echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk echo ' if (state == "cabal") {' >> .colorful.awk echo ' print blue($0)' >> .colorful.awk echo ' } else {' >> .colorful.awk echo ' print $0' >> .colorful.awk echo ' }' >> .colorful.awk echo '}' >> .colorful.awk - cat .colorful.awk - | color_cabal_output () { awk -f $TOP/.colorful.awk } - echo text | color_cabal_output install: - ${CABAL} --version - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]" - TEST=--enable-tests - BENCH=--enable-benchmarks - GHCHEAD=${GHCHEAD-false} - rm -f $CABALHOME/config - | echo "verbose: normal +nowrap +markoutput" >> $CABALHOME/config echo "remote-build-reporting: anonymous" >> $CABALHOME/config echo "remote-repo-cache: $CABALHOME/packages" >> $CABALHOME/config echo "logs-dir: $CABALHOME/logs" >> $CABALHOME/config echo "world-file: $CABALHOME/world" >> $CABALHOME/config echo "extra-prog-path: $CABALHOME/bin" >> $CABALHOME/config echo "symlink-bindir: $CABALHOME/bin" >> $CABALHOME/config echo "installdir: $CABALHOME/bin" >> $CABALHOME/config echo "build-summary: $CABALHOME/logs/build.log" >> $CABALHOME/config echo "store-dir: $CABALHOME/store" >> $CABALHOME/config echo "install-dirs user" >> $CABALHOME/config echo " prefix: $CABALHOME" >> $CABALHOME/config echo "repository hackage.haskell.org" >> $CABALHOME/config echo " url: http://hackage.haskell.org/" >> $CABALHOME/config - cat $CABALHOME/config - rm -fv cabal.project cabal.project.local cabal.project.freeze - travis_retry ${CABAL} v2-update -v # Generate cabal.project - rm -rf cabal.project cabal.project.local cabal.project.freeze - touch cabal.project - | echo 'packages: "."' >> cabal.project - | echo "write-ghc-environment-files: always" >> cabal.project - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(twitter-conduit)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done" - cat cabal.project || true - cat cabal.project.local || true - if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi - ${CABAL} v2-freeze -w ${HC} ${TEST} ${BENCH} | color_cabal_output - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'" - rm cabal.project.freeze - ${CABAL} v2-build -w ${HC} ${TEST} ${BENCH} --dep -j2 all | color_cabal_output - ${CABAL} v2-build -w ${HC} --disable-tests --disable-benchmarks --dep -j2 all | color_cabal_output script: - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX) # Packaging... - ${CABAL} v2-sdist all | color_cabal_output # Unpacking... - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/ - cd ${DISTDIR} || false - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \; # Generate cabal.project - rm -rf cabal.project cabal.project.local cabal.project.freeze - touch cabal.project - | echo 'packages: "twitter-conduit-*/*.cabal"' >> cabal.project - | echo "write-ghc-environment-files: always" >> cabal.project - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(twitter-conduit)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done" - cat cabal.project || true - cat cabal.project.local || true # Building... # this builds all libraries and executables (without tests/benchmarks) - ${CABAL} v2-build -w ${HC} --disable-tests --disable-benchmarks all | color_cabal_output # Building with tests and benchmarks... # build & run tests, build benchmarks - ${CABAL} v2-build -w ${HC} ${TEST} ${BENCH} all | color_cabal_output # Testing... - ${CABAL} v2-test -w ${HC} ${TEST} ${BENCH} all | color_cabal_output # cabal check... - (cd twitter-conduit-* && ${CABAL} -vnormal check) # haddock... - ${CABAL} v2-haddock -w ${HC} ${TEST} ${BENCH} all | color_cabal_output # Building without installed constraints for packages in global-db... - rm -f cabal.project.local - ${CABAL} v2-build -w ${HC} --disable-tests --disable-benchmarks all | color_cabal_output # REGENDATA ["twitter-conduit.cabal","-o",".travis.yml"] # EOF notifications: slack: secure: HUt3eZH5IBJXlgH1/ob+iMqaQRSloqT3VY0rMLmD7gOMpUFm3a5/XYejqD+hd7EYQRa/Z1YCpmIBe3RYGPt6TnNGvIFdcJFFO7lOs7Ooxp03yB5kAMw9eACv/FNJPd2aIjYdKGbSab4XY4s9wW0GewYv0FHrNFWj81Rhl8C4fGg= twitter-conduit-0.5.0/README.md0000644000000000000000000000466013652033050014331 0ustar0000000000000000# twitter-conduit: An Conduit based Twitter API library for Haskell # [![Travis](https://img.shields.io/travis/himura/twitter-conduit/master.svg)](https://travis-ci.org/himura/twitter-conduit) [![Hackage-Deps](https://img.shields.io/hackage-deps/v/twitter-conduit.svg)](http://packdeps.haskellers.com/feed?needle=twitter-conduit) ## About ## This is an conduit based Twitter API library for Haskell, including Streaming API supports. Documentation is available in [hackage](http://hackage.haskell.org/package/twitter-conduit). ## Usage ## $ cabal update $ cabal install twitter-conduit ## Quick Start ## For a runnable example, see [sample/simple.hs](https://github.com/himura/twitter-conduit/blob/master/sample/simple.hs). You can find other various examples in [sample](https://github.com/himura/twitter-conduit/tree/master/sample/) directory. ## Run Samples ## ### Build ### If you would like to use cabal sandbox, prepare sandbox as below: ~~~~ $ cabal sandbox init ~~~~ and then, ~~~~ $ cabal install --only-dependencies -fbuild-samples $ cabal configure -fbuild-samples $ cabal build ~~~~ ### Run ### First, you must obtain consumer key and secret from [Twitter Application Management](https://apps.twitter.com/) page, and you have to set those values to environment variables as shown below: ~~~~ $ export OAUTH_CONSUMER_KEY="YOUR APPLICATION CONSUMER KEY" $ export OAUTH_CONSUMER_SECRET="YOUR APPLICATION CONSUMER SECRET" ~~~~ Before you run examples, you must prepare OAuth access token and secret. You can obtain access token and secret by using either PIN or web callback. If you would like to use the PIN method, you run simply as below, and follow instructions: ~~~~ $ cabal run oauth_pin ~~~~ On the other hand, If you would like to use the callback method, do as follows: ~~~~ $ cabal run oauth_callback ~~~~ and open http://localhost:3000/signIn in your browser. In both cases, you can obtain `OAUTH_ACCESS_TOKEN` and `OAUTH_ACCESS_SECRET` variables, then you should set those values to environment variables as shown below: ~~~~ $ export OAUTH_ACCESS_TOKEN="YOUR ACCESS TOKEN" $ export OAUTH_ACCESS_SECRET="YOUR ACCESS SECRET" ~~~~ Finally, you can access Twitter UserStream as follows: ~~~~ $ cabal run userstream ~~~~ ## Examples ## TODO ## Authors and Credits ## `twitter-conduit` initially was written by Takahiro Himura. We would like to thank everyone who supported and contributed to the development of this library. twitter-conduit-0.5.0/ChangeLog.md0000644000000000000000000000730513652033050015222 0ustar0000000000000000## 0.5.0 * Support for OverloadedLabels `twitter-conduit` now supports the OverloadedLabels extensions for overloaded parameters in `APIRequest` (e.g.: `#count`, `#max_id`). We can now write: ```haskell homeTimeline & #count ?~ 200 ``` instead of: ```haskell import qualified Web.Twitter.Conduit.Parameters as P homeTimeline & P.count ?~ 200 ``` NOTE: See `Web.Twitter.Conduit.ParametersDeprecated` module if you would like to use classic value lenses. * Drop supports conduit < 1.3 and http-conduit < 2.3 [#69](https://github.com/himura/twitter-conduit/pull/69). * `Web.Twitter.Conduit.Status` is no longer re-exported by Web.Twitter.Conduit in order to avoid name conflictions (e.g. `update`, `lookup`) [#71](https://github.com/himura/twitter-conduit/pull/71). * Add alias for functions in `Web.Twitter.Conduit.Status` with statuses- prefix [#71](https://github.com/himura/twitter-conduit/pull/71). (e.g. `Web.Twitter.Conduit.Api.statusesHomeTimeline` for `Web.Twitter.Conduit.Status.homeTimeline`) * Drop supports network < 2.6 [#74](https://github.com/himura/twitter-conduit/pull/74). * Support `tweet_mode` parameter [#72](https://github.com/himura/twitter-conduit/pull/72). ## 0.4.0 * Follow direct message API changes [#65](https://github.com/himura/twitter-conduit/pull/65) [#62](https://github.com/himura/twitter-conduit/pull/62) * Changed WithCursor type [5b9e9d7a](https://github.com/himura/twitter-conduit/commit/5b9e9d7a13d33327fe637cae8e2359a38fce92b5) * Added type parameter to WithCursor to supports `Text` as the next cursor type. * Changed {previous,next}Cursor in WithCursor to be optional * Changed APIRequest type to take HTTP Method [f25fd9b3](https://github.com/himura/twitter-conduit/commit/f25fd9b3b860032f384d01b3457ea896e596366b) ## 0.3.0 * Upgrade http-conduit dependencies to: http-conduit >= 2.0 && < 2.4 [#59](https://github.com/himura/twitter-conduit/pull/59) ## 0.2.2 * Upgrade http-conduit and http-client dependencies to: http-conduit >= 2.1.8 && http-client >= 0.4.30 [#51](https://github.com/himura/twitter-conduit/pull/51) * Added `include_email` parameter to `AccountVerifyCredentials` [#49](https://github.com/himura/twitter-conduit/pull/49) * Added `extAltText` parameter to `showId` [#50](https://github.com/himura/twitter-conduit/pull/50) ## 0.2.1 * Added `fullText` parameter to direct message calls [#47](https://github.com/himura/twitter-conduit/pull/47) * Replaced `SearchStatus` with `Status` type [#46](https://github.com/himura/twitter-conduit/pull/46) * Added `accountUpdateProfile` [#45](https://github.com/himura/twitter-conduit/pull/45) * Added `listsMembersCreateAll` and `listsMembersDestroyAll` * Parameter lenses in `Web.Twitter.Conduit` re-exported from `Web.Twitter.Conduit.Parameters` are deprecated ## 0.2.0 * Changed the signature of functions defined in Web.Twitter.Conduit.Base, because Manager of http-conduit 2.0 and later does not need MonadResource. [#43](https://github.com/himura/twitter-conduit/issues/43) * Removed `TwitterBaseM` * Removed `TW` monad * Re-exported `OAuth (..)` and `Credential (..)` from authenticate-oauth * Re-exported `def` from data-default * Re-exported `Manager`, `newManager` and `tlsManagerSettings` from http-conduit ## 0.1.3 * Make TWToken and TWInfo an instance of Read and Typeable [#42](https://github.com/himura/twitter-conduit/issues/42) ## 0.1.2 * Streaming API: Support multiple filters [#41](https://github.com/himura/twitter-conduit/issues/41) * Include parameters in body for POST requests [#40](https://github.com/himura/twitter-conduit/issues/40) ## 0.1.1.1 * Fix warnings on GHC 7.10 * Fix doctest when twitter-feed package exists ## 0.1.1 * Add `sourceWithSearchResult` and `sourceWithSearchResult'` twitter-conduit-0.5.0/Warning.hs0000644000000000000000000000040013652033050014777 0ustar0000000000000000module Warning {-# WARNING ["You are configuring this package without cabal-doctest installed.", "The doctests test-suite will not work as a result.", "To fix this, install cabal-doctest before configuring."] #-} () where twitter-conduit-0.5.0/sample/LICENSE0000644000000000000000000000246013652033050015334 0ustar0000000000000000Copyright (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. twitter-conduit-0.5.0/sample/twitter-conduit-sample.cabal0000644000000000000000000001012613652033050021735 0ustar0000000000000000name: twitter-conduit-sample version: 0.1 license: BSD3 license-file: LICENSE author: Takahiro HIMURA maintainer: Takahiro HIMURA synopsis: Twitter API package with conduit interface and Streaming API support. category: Web, Conduit stability: Experimental cabal-version: >= 1.10 build-type: Simple homepage: https://github.com/himura/twitter-conduit flag network-uri description: Get Network.URI from the network-uri package default: True library ghc-options: -Wall hs-source-dirs: common build-depends: base >= 4.5 && < 5 , bytestring , case-insensitive , conduit , conduit-extra , containers , directory , filepath , http-conduit >= 2.3.0 , lens , process , resourcet , text , transformers , twitter-conduit , twitter-types-lens if flag(network-uri) build-depends: network-uri >= 2.6 else build-depends: network < 2.6 default-language: Haskell2010 exposed-modules: Common executable fav main-is: fav.hs hs-source-dirs: . build-depends: base >= 4.5 && < 5 , lens , twitter-conduit , twitter-conduit-sample default-language: Haskell2010 ghc-options: -Wall executable oauth_callback main-is: oauth_callback.hs hs-source-dirs: . build-depends: base >= 4.5 && < 5 , authenticate-oauth , bytestring , containers , http-types , scotty >= 0.7 , text , transformers , twitter-conduit default-language: Haskell2010 ghc-options: -Wall executable oauth_pin main-is: oauth_pin.hs hs-source-dirs: . build-depends: base >= 4.5 && < 5 , authenticate-oauth , bytestring , text , twitter-conduit default-language: Haskell2010 ghc-options: -Wall executable oslist main-is: oslist.hs hs-source-dirs: . build-depends: base >= 4.5 && < 5 , conduit , containers , twitter-conduit , twitter-conduit-sample default-language: Haskell2010 ghc-options: -Wall executable post main-is: post.hs hs-source-dirs: . build-depends: base >= 4.5 && < 5 , text , twitter-conduit , twitter-conduit-sample default-language: Haskell2010 ghc-options: -Wall executable postWithMedia main-is: postWithMedia.hs hs-source-dirs: . build-depends: base >= 4.5 && < 5 , text , twitter-conduit , twitter-conduit-sample default-language: Haskell2010 ghc-options: -Wall executable postWithMultipleMedia main-is: postWithMultipleMedia.hs hs-source-dirs: . build-depends: base >= 4.5 && < 5 , lens , text , twitter-conduit , twitter-conduit-sample , twitter-types-lens default-language: Haskell2010 ghc-options: -Wall executable search main-is: search.hs hs-source-dirs: . build-depends: base >= 4.5 && < 5 , lens , text , twitter-conduit , twitter-conduit-sample , twitter-types-lens default-language: Haskell2010 ghc-options: -Wall executable searchSource main-is: searchSource.hs hs-source-dirs: . build-depends: base >= 4.5 && < 5 , conduit , lens , text , twitter-conduit , twitter-conduit-sample , twitter-types-lens default-language: Haskell2010 ghc-options: -Wall executable simple main-is: simple.hs hs-source-dirs: . build-depends: base >= 4.5 && < 5 , authenticate-oauth , bytestring , conduit , lens , resourcet , text , twitter-conduit , twitter-types-lens default-language: Haskell2010 ghc-options: -Wall executable userstream main-is: userstream.hs hs-source-dirs: . build-depends: base >= 4.5 && < 5 , conduit , conduit-extra , directory , filepath , http-conduit , lens , process , resourcet , text , transformers , twitter-conduit , twitter-conduit-sample , twitter-types-lens default-language: Haskell2010 ghc-options: -Wall twitter-conduit-0.5.0/sample/fav.hs0000644000000000000000000000074213652033050015440 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Web.Twitter.Conduit import Common import Control.Lens import System.Environment main :: IO () main = do [statusIdStr] <- getArgs twInfo <- getTWInfoFromEnv mgr <- newManager tlsManagerSettings let sId = read statusIdStr targetStatus <- call twInfo mgr $ statusesShowId sId putStrLn $ "Favorite Tweet: " ++ targetStatus ^. to show res <- call twInfo mgr $ favoritesCreate sId print res twitter-conduit-0.5.0/sample/oslist.hs0000644000000000000000000000145413652033050016202 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Web.Twitter.Conduit import Common import Data.Conduit import qualified Data.Conduit.List as CL import qualified Data.Map as M import System.Environment main :: IO () main = do [screenName] <- getArgs twInfo <- getTWInfoFromEnv mgr <- newManager tlsManagerSettings let sn = ScreenNameParam screenName folids <- runConduit $ sourceWithCursor twInfo mgr (followersIds sn) .| CL.consume friids <- runConduit $ sourceWithCursor twInfo mgr (friendsIds sn) .| CL.consume let folmap = M.fromList $ map (flip (,) True) folids os = filter (\uid -> M.notMember uid folmap) friids bo = filter (\usr -> M.member usr folmap) friids putStrLn "one sided:" print os putStrLn "both following:" print bo twitter-conduit-0.5.0/sample/search.hs0000644000000000000000000000121113652033050016121 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Web.Twitter.Conduit import Web.Twitter.Types.Lens import Common import Control.Lens import qualified Data.Text as T import System.Environment main :: IO () main = do [keyword] <- getArgs twInfo <- getTWInfoFromEnv mgr <- newManager tlsManagerSettings res <- call twInfo mgr $ search $ T.pack keyword let metadata = res ^. searchResultSearchMetadata putStrLn $ "search completed in: " ++ metadata ^. searchMetadataCompletedIn . to show putStrLn $ "search result max id: " ++ metadata ^. searchMetadataMaxId . to show print $ res ^. searchResultStatuses twitter-conduit-0.5.0/sample/userstream.hs0000644000000000000000000000606013652033050017055 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternGuards #-} import Web.Twitter.Conduit import Web.Twitter.Types.Lens import Common import Control.Lens import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Resource import Data.Conduit import qualified Data.Conduit.Binary as CB import qualified Data.Conduit.List as CL import qualified Data.Text as T import qualified Data.Text.IO as T import Network.HTTP.Conduit as HTTP import System.Directory import System.FilePath import System.Process 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 = do twInfo <- getTWInfoFromEnv mgr <- newManager tlsManagerSettings runResourceT $ do src <- stream twInfo mgr userstream runConduit $ src .| CL.mapM_ (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 $ do req <- parseRequest url mgr <- newManager tlsManagerSettings runResourceT $ do body <- http req mgr runConduit $ HTTP.responseBody body .| CB.sinkFile fname return fname twitter-conduit-0.5.0/sample/postWithMedia.hs0000644000000000000000000000066313652033050017447 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Web.Twitter.Conduit import Common import qualified Data.Text as T import System.Environment main :: IO () main = do [status, filepath] <- getArgs putStrLn $ "Post message: " ++ status twInfo <- getTWInfoFromEnv mgr <- newManager tlsManagerSettings res <- call twInfo mgr $ statusesUpdateWithMedia (T.pack status) (MediaFromFile filepath) print res twitter-conduit-0.5.0/sample/Setup.hs0000644000000000000000000000005613652033050015762 0ustar0000000000000000import Distribution.Simple main = defaultMain twitter-conduit-0.5.0/sample/post.hs0000644000000000000000000000067313652033050015654 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Web.Twitter.Conduit import Common import qualified Data.Text as T import qualified Data.Text.IO as T import System.Environment main :: IO () main = do status <- T.concat . map T.pack <$> getArgs T.putStrLn $ "Post message: " <> status twInfo <- getTWInfoFromEnv mgr <- newManager tlsManagerSettings res <- call twInfo mgr $ statusesUpdate status print res twitter-conduit-0.5.0/sample/searchSource.hs0000644000000000000000000000142313652033050017307 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Web.Twitter.Conduit import Web.Twitter.Types.Lens import Common import Control.Lens import Data.Conduit import qualified Data.Conduit.List as CL import qualified Data.Text as T import System.Environment main :: IO () main = do [num, keyword] <- getArgs twInfo <- getTWInfoFromEnv mgr <- newManager tlsManagerSettings res <- sourceWithSearchResult twInfo mgr $ searchTweets $ T.pack keyword let metadata = res ^. searchResultSearchMetadata putStrLn $ "search completed in: " ++ metadata ^. searchMetadataCompletedIn . to show putStrLn $ "search result max id: " ++ metadata ^. searchMetadataMaxId . to show runConduit $ res ^. searchResultStatuses .| CL.isolate (read num) .| CL.mapM_ print twitter-conduit-0.5.0/sample/oauth_pin.hs0000644000000000000000000000341513652033050016652 0ustar0000000000000000{-# 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 hiding (lookup) import Web.Authenticate.OAuth as OA import qualified Data.ByteString.Char8 as S8 import Data.Maybe 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 :: OAuth -- ^ OAuth Consumer key and secret -> Manager -> IO 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 = do putStrLn $ "browse URL: " ++ url putStr "> what was the PIN twitter provided you with? " hFlush stdout S8.getLine main :: IO () main = do tokens <- getTokens mgr <- newManager tlsManagerSettings Credential cred <- authorize tokens mgr 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) <> "\"" ] twitter-conduit-0.5.0/sample/oauth_callback.hs0000644000000000000000000000654013652033050017622 0ustar0000000000000000{-# 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 import qualified Web.Authenticate.OAuth as OA 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.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 mgr <- newManager tlsManagerSettings putStrLn $ "browse URL: http://localhost:3000/signIn" scotty 3000 $ app tokens mgr 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 -> Manager -> ScottyM () app tokens mgr = do get "/callback" $ do temporaryToken <- param "oauth_token" oauthVerifier <- param "oauth_verifier" mcred <- liftIO $ takeCredential temporaryToken usersToken case mcred of Just cred -> do accessTokens <- OA.getAccessToken tokens (OA.insert "oauth_verifier" oauthVerifier cred) mgr 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 <- OA.getTemporaryCredential tokens mgr 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." twitter-conduit-0.5.0/sample/postWithMultipleMedia.hs0000644000000000000000000000216713652033050021164 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedLabels #-} module Main where import Web.Twitter.Conduit import Web.Twitter.Types.Lens import Common import Control.Lens import Control.Monad import qualified Data.Text as T import System.Environment import System.Exit (exitFailure) import System.IO main :: IO () main = do (status:filepathList) <- getArgs when (length filepathList > 4) $ do hPutStrLn stderr $ "You can upload upto 4 images in a single tweet, but we got " ++ show (length filepathList) ++ " images. abort." exitFailure twInfo <- getTWInfoFromEnv mgr <- newManager tlsManagerSettings uploadedMediaList <- forM filepathList $ \filepath -> do putStrLn $ "Upload media: " ++ filepath ret <- call twInfo mgr $ mediaUpload (MediaFromFile filepath) putStrLn $ "Upload completed: media_id: " ++ ret ^. uploadedMediaId . to show ++ ", filepath: " ++ filepath return ret putStrLn $ "Post message: " ++ status res <- call twInfo mgr $ statusesUpdate (T.pack status) & #media_ids ?~ (uploadedMediaList ^.. traversed . uploadedMediaId) print res twitter-conduit-0.5.0/sample/simple.hs0000644000000000000000000000405713652033050016160 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE FlexibleContexts #-} module Main where import Web.Twitter.Conduit import Web.Twitter.Types.Lens import Control.Lens import qualified Data.ByteString.Char8 as B8 import Data.Conduit import qualified Data.Conduit.List as CL import qualified Data.Text as T import qualified Data.Text.IO as T import System.IO (hFlush, stdout) import qualified Web.Authenticate.OAuth as OA tokens :: OAuth tokens = twitterOAuth { oauthConsumerKey = error "You MUST specify oauthConsumerKey parameter." , oauthConsumerSecret = error "You MUST specify oauthConsumerSecret parameter." } authorize :: OAuth -- ^ OAuth Consumer key and secret -> (String -> IO String) -- ^ PIN prompt -> Manager -> IO 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 getTWInfo :: Manager -> IO TWInfo getTWInfo mgr = do Credential cred <- authorize tokens getPIN mgr let cred' = filter (\(k, _) -> k == "oauth_token" || k == "oauth_token_secret") cred return $ setCredential tokens (Credential cred') def where getPIN url = do putStrLn $ "browse URL: " ++ url putStr "> what was the PIN twitter provided you with? " hFlush stdout getLine main :: IO () main = do mgr <- newManager tlsManagerSettings twInfo <- getTWInfo mgr putStrLn $ "# your home timeline (up to 800 tweets):" runConduit $ sourceWithMaxId twInfo mgr (statusesHomeTimeline & #count ?~ 200) .| CL.isolate 800 .| CL.mapM_ (\status -> do T.putStrLn $ T.concat [ T.pack . show $ status ^. statusId , ": " , status ^. statusUser . userScreenName , ": " , status ^. statusText ]) twitter-conduit-0.5.0/sample/common/Common.hs0000644000000000000000000000326313652033050017405 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} module Common where import Web.Twitter.Conduit import Control.Applicative import Control.Lens import qualified Data.ByteString.Char8 as S8 import qualified Data.CaseInsensitive as CI import qualified Data.Map as M import Network.HTTP.Conduit import qualified Network.URI as URI import System.Environment getOAuthTokens :: IO (OAuth, Credential) getOAuthTokens = do consumerKey <- getEnv' "OAUTH_CONSUMER_KEY" consumerSecret <- getEnv' "OAUTH_CONSUMER_SECRET" accessToken <- getEnv' "OAUTH_ACCESS_TOKEN" accessSecret <- getEnv' "OAUTH_ACCESS_SECRET" let oauth = twitterOAuth { oauthConsumerKey = consumerKey , oauthConsumerSecret = consumerSecret } cred = Credential [ ("oauth_token", accessToken) , ("oauth_token_secret", accessSecret) ] return (oauth, cred) where getEnv' = (S8.pack <$>) . getEnv getProxyEnv :: IO (Maybe Proxy) getProxyEnv = do env <- M.fromList . over (mapped . _1) CI.mk <$> getEnvironment let u = M.lookup "https_proxy" env <|> M.lookup "http_proxy" env <|> M.lookup "proxy" env >>= URI.parseURI >>= URI.uriAuthority return $ Proxy <$> (S8.pack . URI.uriRegName <$> u) <*> (parsePort . URI.uriPort <$> u) where parsePort :: String -> Int parsePort [] = 8080 parsePort (':':xs) = read xs parsePort xs = error $ "port number parse failed " ++ xs getTWInfoFromEnv :: IO TWInfo getTWInfoFromEnv = do pr <- getProxyEnv (oa, cred) <- getOAuthTokens return $ (setCredential oa cred def) { twProxy = pr }