twitter-conduit-0.6.1/Web/0000755000000000000000000000000014150466243013573 5ustar0000000000000000twitter-conduit-0.6.1/Web/Twitter/0000755000000000000000000000000014150466243015235 5ustar0000000000000000twitter-conduit-0.6.1/Web/Twitter/Conduit/0000755000000000000000000000000014150466243016642 5ustar0000000000000000twitter-conduit-0.6.1/Web/Twitter/Conduit/Request/0000755000000000000000000000000014150466243020272 5ustar0000000000000000twitter-conduit-0.6.1/sample/0000755000000000000000000000000014150466243014337 5ustar0000000000000000twitter-conduit-0.6.1/sample/common/0000755000000000000000000000000014150466243015627 5ustar0000000000000000twitter-conduit-0.6.1/tests/0000755000000000000000000000000014150466243014220 5ustar0000000000000000twitter-conduit-0.6.1/Web/Twitter/Conduit.hs0000644000000000000000000001716614150466243017211 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 -- ** Authentication -- $auth -- ** How to call API -- $call -- *** How to specify API parameters -- $parameter -- *** Conduit API: Recursive API call with changing cursor parameter -- $conduit -- * 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 (..), TweetMode (..), -- * 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 Control.Lens import Control.Monad.IO.Class import Data.Conduit import qualified Data.Conduit.List as CL import qualified Data.Text as T import qualified Data.Text.IO as T {-# 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 OverloadedLabels #-\} -- {\-# 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 -- @ -- $auth -- -- 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' -- @ -- $call -- -- Twitter API requests are performed by 'call' function. -- For example, -- could be obtained by: -- -- @ -- mgr \<- 'newManager' 'tlsManagerSettings' -- timeline \<- 'call' twInfo mgr 'statusesHomeTimeline' -- @ -- -- 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 '$' 'statusesHomeTimeline' '&' #count '?~' 200 -- @ -- $parameter -- -- The parameters which can be specified for this API, is able to be obtained from type parameters of APIRequest. -- For example, -- -- @ -- 'statusesHomeTimeline' :: -- 'APIRequest' 'StatusesHomeTimeline' ['Web.Twitter.Types.Status'] -- @ -- -- - The 2nd type parameter of 'APIRequest' represents acceptable parameters in this API request. -- - The 3nd type parameter of 'APIRequest' denotes a return type of this API request. -- -- The 2nd type parameter of 'statusesHomeTimeline' is 'StatusesHomeTimeline' defined as below: -- -- @ -- type StatusesHomeTimeline = -- '[ "count" ':= Integer -- , "since_id" ':= Integer -- , "max_id" ':= Integer -- , "trim_user" ':= Bool -- , "exclude_replies" ':= Bool -- , "contributor_details" ':= Bool -- , "include_entities" ':= Bool -- , "tweet_mode" ':= TweetMode -- ] -- @ -- -- Each element of list represents the name and type of API parameters. -- You can specify those parameter with OverloadedLabels extension. -- In the above example, it shows that 'statusesHomeTimeline' API supports "tweet_mode" parameter with 'TweetMode' type, so -- you can specify "tweet_mode" parameter as: -- -- @ -- 'statusesHomeTimeline' & #tweet_mode ?~ 'Extended' -- @ -- $conduit -- -- 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 \<- -- 'runConduit' $ -- 'sourceWithMaxId' twInfo mgr ('friendsList' ('ScreenNameParam' \"thimura\") '&' #count '?~' 200) -- '.|' 'CL.consume' -- @ -- -- Statuses APIs, for instance, 'homeTimeline', are also wrapped by 'sourceWithMaxId'. -- -- For example, you can print 60 tweets from your home timeline, as below: -- -- @ -- main :: IO () -- main = do -- mgr \<- 'newManager' 'tlsManagerSettings' -- 'runConduit' $ 'sourceWithMaxId' twInfo mgr 'homeTimeline' -- '.|' CL.isolate 60 -- '.|' CL.mapM_ -- (\\status -> do -- T.putStrLn $ -- T.concat -- [ T.pack . show $ status ^. statusId -- , \": \" -- , status ^. statusUser . userScreenName -- , \": \" -- , status ^. statusText -- ] -- ) -- @ twitter-conduit-0.6.1/Web/Twitter/Conduit/Lens.hs0000644000000000000000000000414014150466243020076 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 (..), ) where import Control.Lens import Data.Text (Text) import Network.HTTP.Types (ResponseHeaders, Status) 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.6.1/Web/Twitter/Conduit/Types.hs0000644000000000000000000000362714150466243020312 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} module Web.Twitter.Conduit.Types ( TWToken (..), TWInfo (..), twitterOAuth, setCredential, ) where import Data.Default import Data.Typeable (Typeable) import Network.HTTP.Conduit import Web.Authenticate.OAuth 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.6.1/Web/Twitter/Conduit/Api.hs0000644000000000000000000013332414150466243017715 0ustar0000000000000000{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeOperators #-} module Web.Twitter.Conduit.Api ( -- * Status StatusesMentionsTimeline, statusesMentionsTimeline, StatusesUserTimeline, statusesUserTimeline, StatusesHomeTimeline, statusesHomeTimeline, StatusesRetweetsOfMe, statusesRetweetsOfMe, StatusesRetweetsId, statusesRetweetsId, StatusesShowId, statusesShowId, StatusesDestroyId, statusesDestroyId, StatusesUpdate, statusesUpdate, StatusesRetweetId, statusesRetweetId, StatusesUpdateWithMedia, statusesUpdateWithMedia, StatusesLookup, 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 Web.Twitter.Types import Data.Aeson import Data.Default import qualified Data.Text as T import Data.Time.Calendar (Day) import Network.HTTP.Client.MultipartFormData -- $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' -- @ -- -- >>> statusesMentionsTimeline -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/mentions_timeline.json" [] statusesMentionsTimeline :: APIRequest StatusesMentionsTimeline [Status] statusesMentionsTimeline = 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" ':= TweetMode ] -- | 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\") -- @ -- -- >>> statusesUserTimeline (ScreenNameParam "thimura") -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/user_timeline.json" [("screen_name","thimura")] -- >>> statusesUserTimeline (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")] statusesUserTimeline :: UserParam -> APIRequest StatusesUserTimeline [Status] statusesUserTimeline 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" ':= TweetMode ] -- | 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' -- @ -- -- >>> statusesHomeTimeline -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/home_timeline.json" [] -- >>> statusesHomeTimeline & #count ?~ 200 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/home_timeline.json" [("count","200")] statusesHomeTimeline :: APIRequest StatusesHomeTimeline [Status] statusesHomeTimeline = 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" ':= TweetMode ] -- | 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' -- @ -- -- >>> statusesRetweetsOfMe -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets_of_me.json" [] -- >>> statusesRetweetsOfMe & #count ?~ 100 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets_of_me.json" [("count","100")] statusesRetweetsOfMe :: APIRequest StatusesRetweetsOfMe [Status] statusesRetweetsOfMe = 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" ':= TweetMode ] -- * 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 -- @ -- -- >>> statusesRetweetsId 1234567890 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets/1234567890.json" [] -- >>> statusesRetweetsId 1234567890 & #count ?~ 100 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets/1234567890.json" [("count","100")] statusesRetweetsId :: StatusId -> APIRequest StatusesRetweetsId [RetweetedStatus] statusesRetweetsId status_id = APIRequest "GET" uri def where uri = endpoint ++ "statuses/retweets/" ++ show status_id ++ ".json" type StatusesRetweetsId = '[ "count" ':= Integer , "trim_user" ':= Bool , "tweet_mode" ':= TweetMode ] -- | 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 -- @ -- -- >>> statusesShowId 1234567890 -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/show/1234567890.json" [] -- >>> statusesShowId 1234567890 & #include_my_retweet ?~ True -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/show/1234567890.json" [("include_my_retweet","true")] statusesShowId :: StatusId -> APIRequest StatusesShowId Status statusesShowId 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" ':= TweetMode ] -- | 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 -- @ -- -- >>> statusesDestroyId 1234567890 -- APIRequest "POST" "https://api.twitter.com/1.1/statuses/destroy/1234567890.json" [] statusesDestroyId :: StatusId -> APIRequest StatusesDestroyId Status statusesDestroyId status_id = APIRequest "POST" uri def where uri = endpoint ++ "statuses/destroy/" ++ show status_id ++ ".json" type StatusesDestroyId = '[ "trim_user" ':= Bool , "tweet_mode" ':= TweetMode ] -- | 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\" -- @ -- -- >>> statusesUpdate "Hello World" -- APIRequest "POST" "https://api.twitter.com/1.1/statuses/update.json" [("status","Hello World")] -- >>> statusesUpdate "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")] statusesUpdate :: T.Text -> APIRequest StatusesUpdate Status statusesUpdate 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" ':= TweetMode ] -- | Returns post data which retweets a tweet, specified by ID. -- -- You can perform a search query using 'call': -- -- @ -- res <- 'call' twInfo mgr '$' 'retweetId' 1234567890 -- @ -- -- >>> statusesRetweetId 1234567890 -- APIRequest "POST" "https://api.twitter.com/1.1/statuses/retweet/1234567890.json" [] statusesRetweetId :: StatusId -> APIRequest StatusesRetweetId RetweetedStatus statusesRetweetId status_id = APIRequest "POST" uri def where uri = endpoint ++ "statuses/retweet/" ++ show status_id ++ ".json" type StatusesRetweetId = '[ "trim_user" ':= Bool , "tweet_mode" ':= TweetMode ] -- | 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\") -- @ -- -- >>> statusesUpdateWithMedia "Hello World" (MediaFromFile "/home/fuga/test.jpeg") -- APIRequestMultipart "POST" "https://api.twitter.com/1.1/statuses/update_with_media.json" [("status","Hello World")] statusesUpdateWithMedia :: T.Text -> MediaData -> APIRequest StatusesUpdateWithMedia Status statusesUpdateWithMedia 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" ':= TweetMode ] -- | 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] -- @ -- -- >>> statusesLookup [10] -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/lookup.json" [("id","10")] -- >>> statusesLookup [10, 432656548536401920] -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/lookup.json" [("id","10,432656548536401920")] -- >>> statusesLookup [10, 432656548536401920] & #include_entities ?~ True -- APIRequest "GET" "https://api.twitter.com/1.1/statuses/lookup.json" [("include_entities","true"),("id","10,432656548536401920")] statusesLookup :: [StatusId] -> APIRequest StatusesLookup [Status] statusesLookup ids = APIRequest "GET" (endpoint ++ "statuses/lookup.json") [("id", PVIntegerArray ids)] type StatusesLookup = '[ "include_entities" ':= Bool , "trim_user" ':= Bool , "map" ':= Bool , "tweet_mode" ':= TweetMode ] -- | 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 :: -- | search string T.Text -> APIRequest SearchTweets (SearchResult [Status]) searchTweets q = APIRequest "GET" (endpoint ++ "search/tweets.json") [("q", PVString q)] type SearchTweets = '[ "lang" ':= T.Text , "locale" ':= T.Text , "result_type" ':= T.Text , "count" ':= Integer , "until" ':= Day , "since_id" ':= Integer , "max_id" ':= Integer , "include_entities" ':= Bool , "tweet_mode" ':= TweetMode ] -- | Alias of 'searchTweets', for backward compatibility search :: -- | search string T.Text -> 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 , "tweet_mode" ':= TweetMode ] -- | 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 , "tweet_mode" ':= TweetMode ] -- | Returns post data unfavorites the status specified in the ID parameter 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 , "tweet_mode" ':= TweetMode ] -- | 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 , "tweet_mode" ':= TweetMode ] -- | 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 -> -- | is public Bool -> -- | description Maybe T.Text -> 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 :: -- | list name T.Text -> -- | whether public(True) or private(False) Bool -> -- | the description to give the list Maybe T.Text -> 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 twitter-conduit-0.6.1/Web/Twitter/Conduit/Stream.hs0000644000000000000000000000764614150466243020446 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeOperators #-} 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.Base 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 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 :: -- | keyword T.Text -> APIRequest StatusesFilter StreamingAPI statusesFilterByTrack keyword = statusesFilter [Track [keyword]] type StatusesFilter = '[ "language" ':= T.Text , "filter_level" ':= T.Text , "stall_warnings" ':= Bool ] twitter-conduit-0.6.1/Web/Twitter/Conduit/Status.hs0000644000000000000000000000503714150466243020466 0ustar0000000000000000module Web.Twitter.Conduit.Status ( -- * Notice -- $notice -- * Timelines mentionsTimeline, userTimeline, homeTimeline, retweetsOfMe, -- * Tweets retweetsId, showId, destroyId, update, retweetId, updateWithMedia, lookup, ) where import Data.Text (Text) import Web.Twitter.Conduit.Api import Web.Twitter.Conduit.Parameters import Web.Twitter.Conduit.Request import Web.Twitter.Types import Prelude hiding (lookup) -- $notice -- -- This module provides aliases of statuses API, for backward compatibility. mentionsTimeline :: APIRequest StatusesMentionsTimeline [Status] mentionsTimeline = statusesMentionsTimeline {-# DEPRECATED mentionsTimeline "Please use Web.Twitter.Conduit.API.statusesMentionsTimeline" #-} userTimeline :: UserParam -> APIRequest StatusesUserTimeline [Status] userTimeline = statusesUserTimeline {-# DEPRECATED userTimeline "Please use Web.Twitter.Conduit.API.statusesUserTimeline" #-} homeTimeline :: APIRequest StatusesHomeTimeline [Status] homeTimeline = statusesHomeTimeline {-# DEPRECATED homeTimeline "Please use Web.Twitter.Conduit.API.statusesHomeTimeline" #-} retweetsOfMe :: APIRequest StatusesRetweetsOfMe [Status] retweetsOfMe = statusesRetweetsOfMe {-# DEPRECATED retweetsOfMe "Please use Web.Twitter.Conduit.API.statusesRetweetsOfMe" #-} retweetsId :: StatusId -> APIRequest StatusesRetweetsId [RetweetedStatus] retweetsId = statusesRetweetsId {-# DEPRECATED retweetsId "Please use Web.Twitter.Conduit.API.statusesRetweetsId" #-} showId :: StatusId -> APIRequest StatusesShowId Status showId = statusesShowId {-# DEPRECATED showId "Please use Web.Twitter.Conduit.API.statusesShowId" #-} destroyId :: StatusId -> APIRequest StatusesDestroyId Status destroyId = statusesDestroyId {-# DEPRECATED destroyId "Please use Web.Twitter.Conduit.API.statusesDestroyId" #-} update :: Text -> APIRequest StatusesUpdate Status update = statusesUpdate {-# DEPRECATED update "Please use Web.Twitter.Conduit.API.statusesUpdate" #-} retweetId :: StatusId -> APIRequest StatusesRetweetId RetweetedStatus retweetId = statusesRetweetId {-# DEPRECATED retweetId "Please use Web.Twitter.Conduit.API.statusesRetweetId" #-} updateWithMedia :: Text -> MediaData -> APIRequest StatusesUpdateWithMedia Status updateWithMedia = statusesUpdateWithMedia {-# DEPRECATED updateWithMedia "Please use Web.Twitter.Conduit.API.statusesUpdateWithMedia" #-} lookup :: [StatusId] -> APIRequest StatusesLookup [Status] lookup = statusesLookup {-# DEPRECATED lookup "Please use Web.Twitter.Conduit.API.statusesLookup" #-} twitter-conduit-0.6.1/Web/Twitter/Conduit/Base.hs0000644000000000000000000003234514150466243020057 0ustar0000000000000000{-# 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 GHC.TypeLits (KnownSymbol) import Network.HTTP.Client.MultipartFormData import qualified Network.HTTP.Conduit as HTTP import qualified Network.HTTP.Types as HT import Web.Authenticate.OAuth (signOAuth) 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' :: -- | HTTP request method (GET or POST) HT.Method -> -- | API Resource URL String -> -- | Query HT.SimpleQuery -> 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 => -- | Twitter Setting TWInfo -> 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 => -- | Twitter Setting TWInfo -> 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 => -- | Twitter Setting TWInfo -> 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 ) => -- | Twitter Setting TWInfo -> 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 ) => -- | Twitter Setting TWInfo -> 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 , KnownSymbol ck , HasParam "cursor" Integer supports ) => -- | Twitter Setting TWInfo -> 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 , KnownSymbol ck , HasParam "cursor" Integer supports ) => -- | Twitter Setting TWInfo -> 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 ) => -- | Twitter Setting TWInfo -> 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 ) => -- | Twitter Setting TWInfo -> 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.6.1/Web/Twitter/Conduit/Request.hs0000644000000000000000000000626714150466243020641 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.6.1/Web/Twitter/Conduit/Request/Internal.hs0000644000000000000000000000651114150466243022405 0ustar0000000000000000{-# 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) => -- | key ByteString -> 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 fromLabel = rawParam key where key = S8.pack (symbolVal (Proxy :: Proxy label)) twitter-conduit-0.6.1/Web/Twitter/Conduit/Response.hs0000644000000000000000000000336714150466243021005 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} {-# 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 (ResponseHeaders, Status) 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.6.1/Web/Twitter/Conduit/Cursor.hs0000644000000000000000000000467714150466243020471 0ustar0000000000000000{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Web.Twitter.Conduit.Cursor ( IdsCursorKey, UsersCursorKey, ListsCursorKey, EventsCursorKey, WithCursor (..), ) where import Control.DeepSeq (NFData) import Data.Aeson import Data.Proxy (Proxy (..)) import Data.String import GHC.Generics import GHC.TypeLits (KnownSymbol, Symbol, symbolVal) -- $setup -- >>> import Data.Text -- >>> type UserId = Integer type IdsCursorKey = "ids" type UsersCursorKey = "users" type ListsCursorKey = "lists" type EventsCursorKey = "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 "ids" UserId) -- >>> nextCursor res -- Just 1234567890 -- >>> contents res -- [1111111111] -- -- >>> let Just res = decode "{\"previous_cursor\": 0, \"next_cursor\": 0, \"users\": [1000]}" :: Maybe (WithCursor Integer "users" UserId) -- >>> nextCursor res -- Just 0 -- >>> contents res -- [1000] -- -- >>> let Just res = decode "{\"next_cursor\": \"hogehoge\", \"events\": [1000]}" :: Maybe (WithCursor Text "events" UserId) -- >>> nextCursor res -- Just "hogehoge" -- >>> contents res -- [1000] data WithCursor cursorType (cursorKey :: Symbol) wrapped = WithCursor { previousCursor :: Maybe cursorType , nextCursor :: Maybe cursorType , contents :: [wrapped] } deriving (Show, Eq, Generic, Generic1, Functor, Foldable, Traversable) instance (KnownSymbol cursorKey, FromJSON cursorType) => FromJSON1 (WithCursor cursorType cursorKey) where liftParseJSON _ lp = withObject ("WithCursor \"" ++ cursorKeyStr ++ "\"") $ \obj -> WithCursor <$> obj .:? "previous_cursor" <*> obj .:? "next_cursor" <*> (obj .: fromString cursorKeyStr >>= lp) where cursorKeyStr = symbolVal (Proxy :: Proxy cursorKey) instance (KnownSymbol cursorKey, FromJSON cursorType, FromJSON wrapped) => FromJSON (WithCursor cursorType cursorKey wrapped) where parseJSON = parseJSON1 instance (NFData cursorType, NFData wrapped) => NFData (WithCursor cursorType cursorKey wrapped) twitter-conduit-0.6.1/Web/Twitter/Conduit/Parameters.hs0000644000000000000000000000513514150466243021305 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Web.Twitter.Conduit.Parameters ( UserParam (..), UserListParam (..), ListParam (..), MediaData (..), TweetMode (..), mkUserParam, mkUserListParam, mkListParam, ) where import qualified Data.Text as T import Network.HTTP.Client (RequestBody) import Web.Twitter.Conduit.Request.Internal (APIQuery, PV (..), ParameterValue (..)) import Web.Twitter.Types -- $setup -- >>> import Web.Twitter.Conduit.Request.Internal -- Required parameters 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 -- Optional parameters data TweetMode = Extended deriving (Show, Eq) instance ParameterValue TweetMode where wrap Extended = PVString "extended" unwrap = const Extended twitter-conduit-0.6.1/tests/spec_main.hs0000644000000000000000000000006114150466243016507 0ustar0000000000000000import Spec import Test.Hspec main = hspec spec twitter-conduit-0.6.1/tests/Spec.hs0000644000000000000000000000007414150466243015447 0ustar0000000000000000{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --no-main #-} twitter-conduit-0.6.1/tests/ApiSpec.hs0000644000000000000000000001400614150466243016101 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} module ApiSpec 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 Test.Hspec import TestUtils import Web.Twitter.Conduit (TWInfo, accountVerifyCredentials, call, sourceWithCursor, sourceWithMaxId) import Web.Twitter.Conduit.Api import Web.Twitter.Conduit.Lens import qualified Web.Twitter.Conduit.Parameters as Param import Web.Twitter.Types.Lens 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 recent mentions for user" $ do res <- call twInfo mgr statusesMentionsTimeline 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 $ statusesUserTimeline (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 $ statusesUserTimeline (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 $ statusesUserTimeline (Param.ScreenNameParam "thimura") & #count ?~ 200 tl <- runConduit $ 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 statusesHomeTimeline length res `shouldSatisfy` (> 0) describe "showId" $ do it "works for the known tweets" $ do res <- call twInfo mgr $ statusesShowId 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 $ statusesUpdate "おまえの明日が、今日よりもずっと、楽しい事で溢れているようにと、祈っているよ" res1 ^. statusUser . userScreenName `shouldBe` self ^. userScreenName res2 <- call twInfo mgr $ statusesDestroyId (res1 ^. statusId) res2 ^. statusId `shouldBe` res1 ^. statusId describe "lookup" $ do it "works for the known tweets" $ do res <- call twInfo mgr $ statusesLookup [438691466345340928, 477757405942411265] length res `shouldSatisfy` (== 2) (res !! 0) ^. statusId `shouldBe` 438691466345340928 (res !! 1) ^. statusId `shouldBe` 477757405942411265 it "handles extended tweets" $ do res <- call twInfo mgr $ statusesLookup [1128358947772145672] (res !! 0) ^. statusText `shouldBe` "Through the Twitter Developer Labs program, we'll soon preview new versions of GET /tweets and GET /users, followed\8230 https://t.co/9i4c5bUUCu" res <- call twInfo mgr $ statusesLookup [1128358947772145672] & #tweet_mode ?~ Param.Extended (res !! 0) ^. statusText `shouldBe` "Through the Twitter Developer Labs program, we'll soon preview new versions of GET /tweets and GET /users, followed by Tweet streaming, search & metrics. More to come! \128073 https://t.co/rDE48yNiSw https://t.co/oFsvkpnDhS" 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 <- runConduit $ 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 <- runConduit $ src .| CL.consume members ^.. traversed . userScreenName `shouldContain` ["Hackage"] twitter-conduit-0.6.1/tests/BaseSpec.hs0000644000000000000000000000715214150466243016246 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module BaseSpec where import Web.Twitter.Conduit.Base import Web.Twitter.Conduit.Response 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.6.1/tests/TestUtils.hs0000644000000000000000000000164614150466243016523 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.6.1/tests/doctests.hs0000644000000000000000000000043514150466243016406 0ustar0000000000000000module Main where import Build_doctests (flags, module_sources, pkgs) 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.6.1/LICENSE0000644000000000000000000000246014150466243014065 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.6.1/Setup.hs0000644000000000000000000000115214150466243014511 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.6.1/twitter-conduit.cabal0000644000000000000000000000740514150466243017215 0ustar0000000000000000cabal-version: 1.24 name: twitter-conduit version: 0.6.1 license: BSD3 license-file: LICENSE maintainer: Takahiro HIMURA author: HATTORI Hiroki, Hideyuki Tanaka, Takahiro HIMURA stability: Experimental tested-with: ghc ==8.8.4 ghc ==8.10.7 ghc ==9.0.1 homepage: https://github.com/himura/twitter-conduit synopsis: Twitter API package with conduit interface and Streaming API support. 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. category: Web, Conduit build-type: Custom extra-source-files: .gitignore 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 custom-setup setup-depends: base, Cabal >=1.24, cabal-doctest >=1 && <1.1 flag run-integrated-test description: use debug output when running testsuites default: False library 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 default-language: Haskell2010 ghc-options: -Wall build-depends: base >=4.12 && <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, deepseq, 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 test-suite doctests type: exitcode-stdio-1.0 main-is: doctests.hs hs-source-dirs: tests default-language: Haskell2010 build-depends: base, doctest test-suite spec_main type: exitcode-stdio-1.0 main-is: spec_main.hs build-tool-depends: hspec-discover:hspec-discover >=2.3.0 hs-source-dirs: tests other-modules: Spec ApiSpec BaseSpec TestUtils default-language: Haskell2010 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 if flag(run-integrated-test) cpp-options: -DRUN_INTEGRATED_TEST twitter-conduit-0.6.1/.gitignore0000644000000000000000000000044414150466243015050 0ustar0000000000000000# General \#*# .*~ *~ .#* *.swp .DS_Store .gdb_history TAGS # Object files, etc *.a *.o *.so *.hi *.p_hi *.chi *.chs.h *.tix .hpc a.out # autotool autom4te.cache stamp-h1 # misc *.sqlite Main dist/ cabal-dev/ .cabal-sandbox/ cabal.sandbox.config dist-newstyle/ .ghc.environment.* .stack-work twitter-conduit-0.6.1/README.md0000644000000000000000000000512214150466243014335 0ustar0000000000000000# twitter-conduit: An Conduit based Twitter API library for Haskell # ![CI](https://github.com/himura/twitter-conduit/workflows/CI/badge.svg) [![Hackage](https://img.shields.io/hackage/v/twitter-conduit.svg?style=flat)](https://hackage.haskell.org/package/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). ## 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 ### #### Building with Cabal #### ~~~~ $ cabal v2-build all ~~~~ #### Building with Stack #### To build with Stack, you should create top-level `stack.yaml` file first. An example of `stack.yaml` is like below: ```.yaml resolver: lts-18.12 packages: - . - sample extra-deps: - twitter-types-0.11.0 - twitter-types-lens-0.11.0 ``` then, run stack. ``` $ stack 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.6.1/ChangeLog.md0000644000000000000000000001015414150466243015230 0ustar0000000000000000## 0.6.1 * Export TweetMode (Extended) [#92](https://github.com/himura/twitter-conduit/pull/92) ## 0.6.0 * Support extended tweets [#83](https://github.com/himura/twitter-conduit/pull/83) * Remove deprecated module `Web.Twitter.Conduit.ParametersDeprecated` [#81](https://github.com/himura/twitter-conduit/pull/81) ## 0.5.1 * Add result_type to searchTweets [#78](https://github.com/himura/twitter-conduit/pull/78). ## 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.6.1/Warning.hs0000644000000000000000000000042214150466243015015 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.6.1/sample/LICENSE0000644000000000000000000000246014150466243015346 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.6.1/sample/twitter-conduit-sample.cabal0000644000000000000000000001012614150466243021747 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.6.1/sample/fav.hs0000644000000000000000000000074214150466243015452 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Common import Web.Twitter.Conduit 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.6.1/sample/oslist.hs0000644000000000000000000000145514150466243016215 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Common import Web.Twitter.Conduit 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.6.1/sample/search.hs0000644000000000000000000000121214150466243016134 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Common import Web.Twitter.Conduit import Web.Twitter.Types.Lens 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.6.1/sample/userstream.hs0000644000000000000000000000563314150466243017074 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternGuards #-} import Common import Web.Twitter.Conduit import Web.Twitter.Types.Lens 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 :: -- | screen name String -> -- | icon url String -> 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.6.1/sample/postWithMedia.hs0000644000000000000000000000066414150466243017462 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Common import Web.Twitter.Conduit 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.6.1/sample/Setup.hs0000644000000000000000000000005714150466243015775 0ustar0000000000000000import Distribution.Simple main = defaultMain twitter-conduit-0.6.1/sample/post.hs0000644000000000000000000000067414150466243015667 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Common import Web.Twitter.Conduit 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.6.1/sample/searchSource.hs0000644000000000000000000000142414150466243017322 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} module Main where import Common import Web.Twitter.Conduit import Web.Twitter.Types.Lens 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.6.1/sample/oauth_pin.hs0000644000000000000000000000341114150466243016660 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -- Example: -- $ export OAUTH_CONSUMER_KEY="your consumer key" -- $ export OAUTH_CONSUMER_SECRET="your consumer secret" -- $ runhaskell oauth_pin.hs module Main where import qualified Data.ByteString.Char8 as S8 import Data.Maybe import System.Environment import System.IO (hFlush, stdout) import Web.Authenticate.OAuth as OA import Web.Twitter.Conduit 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 Consumer key and secret OAuth -> 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.6.1/sample/oauth_callback.hs0000644000000000000000000000657014150466243017637 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 Control.Monad.IO.Class import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as S8 import Data.IORef import qualified Data.Map as M import Data.Maybe import qualified Data.Text.Lazy as LT import qualified Network.HTTP.Types as HT import System.Environment import System.IO.Unsafe import qualified Web.Authenticate.OAuth as OA import Web.Scotty import Web.Twitter.Conduit 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.6.1/sample/postWithMultipleMedia.hs0000644000000000000000000000217014150466243021170 0ustar0000000000000000{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} module Main where import Common import Web.Twitter.Conduit import Web.Twitter.Types.Lens 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.6.1/sample/simple.hs0000644000000000000000000000416714150466243016174 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} 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 Consumer key and secret OAuth -> -- | PIN prompt (String -> IO String) -> 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.6.1/sample/common/Common.hs0000644000000000000000000000335314150466243017417 0ustar0000000000000000{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} 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}