twitter-conduit 0.6.0 → 0.6.1
raw patch · 12 files changed
+551/−527 lines, 12 filesdep +deepseqdep ~base
Dependencies added: deepseq
Dependency ranges changed: base
Files
- ChangeLog.md +4/−0
- README.md +21/−13
- Web/Twitter/Conduit.hs +92/−24
- Web/Twitter/Conduit/Api.hs +285/−24
- Web/Twitter/Conduit/Base.hs +3/−7
- Web/Twitter/Conduit/Cursor.hs +34/−50
- Web/Twitter/Conduit/Lens.hs +0/−4
- Web/Twitter/Conduit/Request/Internal.hs +2/−9
- Web/Twitter/Conduit/Status.hs +33/−281
- tests/ApiSpec.hs +74/−5
- tests/StatusSpec.hs +0/−107
- twitter-conduit.cabal +3/−3
ChangeLog.md view
@@ -1,3 +1,7 @@+## 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)
README.md view
@@ -10,11 +10,6 @@ Documentation is available in [hackage](http://hackage.haskell.org/package/twitter-conduit). -## Usage ##-- $ cabal update- $ cabal install twitter-conduit- ## Quick Start ## For a runnable example, see [sample/simple.hs](https://github.com/himura/twitter-conduit/blob/master/sample/simple.hs).@@ -24,19 +19,32 @@ ### Build ### -If you would like to use cabal sandbox, prepare sandbox as below:+#### Building with Cabal #### ~~~~-$ cabal sandbox init+$ cabal v2-build all ~~~~ -and then,+#### Building with Stack #### -~~~~-$ cabal install --only-dependencies -fbuild-samples-$ cabal configure -fbuild-samples-$ cabal build-~~~~+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 ###
Web/Twitter/Conduit.hs view
@@ -13,6 +13,18 @@ -- * 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,@@ -38,6 +50,7 @@ MediaData (..), UserListParam (..), UserParam (..),+ TweetMode (..), -- * re-exports OAuth (..),@@ -80,17 +93,22 @@ -- and <http://hackage.haskell.org/package/conduit conduit>. -- All of following examples import modules as below: ----- > {\-# LANGUAGE OverloadedStrings #-\}--- >--- > import Web.Twitter.Conduit--- > import Web.Twitter.Types.Lens--- > import Data.Conduit--- > import qualified Data.Conduit.List as CL--- > import qualified Data.Text as T--- > import qualified Data.Text.IO as T--- > import Control.Monad.IO.Class--- > import Control.Lens+-- @+-- {\-# 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 <https://apps.twitter.com/ Twitter>, -- and prepare 'OAuth' variables as follows: --@@ -132,7 +150,11 @@ -- -- Or, simply as follows: ----- > twInfo = setCredential tokens credential def+-- @+-- twInfo = 'setCredential' tokens credential 'def'+-- @++-- $call -- -- Twitter API requests are performed by 'call' function. -- For example, <https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline GET statuses/home_timeline>@@ -140,7 +162,7 @@ -- -- @ -- mgr \<- 'newManager' 'tlsManagerSettings'--- timeline \<- 'call' twInfo mgr 'homeTimeline'+-- timeline \<- 'call' twInfo mgr 'statusesHomeTimeline' -- @ -- -- The response of 'call' function is wrapped by the suitable type of@@ -153,9 +175,48 @@ -- includes 20 tweets, and you can change the number of tweets by the /count/ parameter. -- -- @--- timeline \<- 'call' twInfo mgr '$' 'homeTimeline' '&' #count '?~' 200+-- 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,@@ -163,24 +224,31 @@ -- or use the conduit wrapper 'sourceWithCursor' as below: -- -- @--- friends \<- 'sourceWithCursor' twInfo mgr ('friendsList' ('ScreenNameParam' \"thimura\") '&' #count '?~' 200) '$$' 'CL.consume'+-- 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 1000 tweets from your home timeline, as below:+-- For example, you can print 60 tweets from your home timeline, as below: -- -- @ -- main :: IO () -- main = do -- mgr \<- 'newManager' 'tlsManagerSettings'--- 'sourceWithMaxId' twInfo mgr 'homeTimeline'--- $= CL.isolate 60--- $$ CL.mapM_ $ \\status -> liftIO $ do--- T.putStrLn $ T.concat [ T.pack . show $ status ^. statusId--- , \": \"--- , status ^. statusUser . userScreenName--- , \": \"--- , status ^. statusText--- ]+-- '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+-- ]+-- ) -- @
Web/Twitter/Conduit/Api.hs view
@@ -6,16 +6,27 @@ 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@@ -165,7 +176,6 @@ import Web.Twitter.Conduit.Parameters import Web.Twitter.Conduit.Request import Web.Twitter.Conduit.Request.Internal-import qualified Web.Twitter.Conduit.Status as Status import Web.Twitter.Types import Data.Aeson@@ -178,6 +188,280 @@ -- >>> :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':@@ -1011,26 +1295,3 @@ mediaBody (MediaRequestBody filename filebody) = partFileRequestBody "media" filename filebody type MediaUpload = EmptyParams--statusesMentionsTimeline :: APIRequest Status.StatusesMentionsTimeline [Status]-statusesMentionsTimeline = Status.mentionsTimeline-statusesUserTimeline :: UserParam -> APIRequest Status.StatusesUserTimeline [Status]-statusesUserTimeline = Status.userTimeline-statusesHomeTimeline :: APIRequest Status.StatusesHomeTimeline [Status]-statusesHomeTimeline = Status.homeTimeline-statusesRetweetsOfMe :: APIRequest Status.StatusesRetweetsOfMe [Status]-statusesRetweetsOfMe = Status.retweetsOfMe-statusesRetweetsId :: StatusId -> APIRequest Status.StatusesRetweetsId [RetweetedStatus]-statusesRetweetsId = Status.retweetsId-statusesShowId :: StatusId -> APIRequest Status.StatusesShowId Status-statusesShowId = Status.showId-statusesDestroyId :: StatusId -> APIRequest Status.StatusesDestroyId Status-statusesDestroyId = Status.destroyId-statusesUpdate :: T.Text -> APIRequest Status.StatusesUpdate Status-statusesUpdate = Status.update-statusesRetweetId :: StatusId -> APIRequest Status.StatusesRetweetId RetweetedStatus-statusesRetweetId = Status.retweetId-statusesUpdateWithMedia :: T.Text -> MediaData -> APIRequest Status.StatusesUpdateWithMedia Status-statusesUpdateWithMedia = Status.updateWithMedia-statusesLookup :: [StatusId] -> APIRequest Status.StatusesLookup [Status]-statusesLookup = Status.lookup
Web/Twitter/Conduit/Base.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}@@ -51,15 +50,12 @@ 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) -#if __GLASGOW_HASKELL__ < 804-import Data.Monoid-#endif- makeRequest :: APIRequest apiName responseType -> IO HTTP.Request@@ -298,7 +294,7 @@ sourceWithCursor :: ( MonadIO m , FromJSON responseType- , CursorKey ck+ , KnownSymbol ck , HasParam "cursor" Integer supports ) => -- | Twitter Setting@@ -322,7 +318,7 @@ -- This function cooperate with instances of 'HasCursorParam'. sourceWithCursor' :: ( MonadIO m- , CursorKey ck+ , KnownSymbol ck , HasParam "cursor" Integer supports ) => -- | Twitter Setting
Web/Twitter/Conduit/Cursor.hs view
@@ -1,10 +1,13 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Web.Twitter.Conduit.Cursor (- CursorKey (..), IdsCursorKey, UsersCursorKey, ListsCursorKey,@@ -12,79 +15,60 @@ WithCursor (..), ) where +import Control.DeepSeq (NFData) import Data.Aeson-import Data.Text (Text)-import Web.Twitter.Types (checkError)+import Data.Proxy (Proxy (..))+import Data.String+import GHC.Generics+import GHC.TypeLits (KnownSymbol, Symbol, symbolVal) -- $setup+-- >>> import Data.Text -- >>> type UserId = Integer -{- ORMOLU_DISABLE -}-class CursorKey a where-#if MIN_VERSION_aeson(2, 0, 0)- cursorKey :: a -> Key-#else- cursorKey :: a -> Text-#endif-{- ORMOLU_ENABLE -}---- | Phantom type to specify the key which point out the content in the response.-data IdsCursorKey--instance CursorKey IdsCursorKey where- cursorKey = const "ids"---- | Phantom type to specify the key which point out the content in the response.-data UsersCursorKey--instance CursorKey UsersCursorKey where- cursorKey = const "users"---- | Phantom type to specify the key which point out the content in the response.-data ListsCursorKey--instance CursorKey ListsCursorKey where- cursorKey = const "lists"--data EventsCursorKey-instance CursorKey EventsCursorKey where- cursorKey = const "events"+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 IdsCursorKey UserId)+-- >>> 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 UsersCursorKey UserId)+-- >>> 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 EventsCursorKey UserId)+-- >>> 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 wrapped = WithCursor+data WithCursor cursorType (cursorKey :: Symbol) wrapped = WithCursor { previousCursor :: Maybe cursorType , nextCursor :: Maybe cursorType , contents :: [wrapped] }- deriving (Show)+ deriving (Show, Eq, Generic, Generic1, Functor, Foldable, Traversable) -instance- (FromJSON wrapped, FromJSON ct, CursorKey c) =>- FromJSON (WithCursor ct c wrapped)- where- parseJSON (Object o) =- checkError o- >> WithCursor <$> o .:? "previous_cursor"- <*> o .:? "next_cursor"- <*> o .: cursorKey (undefined :: c)- parseJSON _ = mempty+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)
Web/Twitter/Conduit/Lens.hs view
@@ -20,10 +20,6 @@ -- * Re-exports TT.TwitterError (..),- TT.CursorKey (..),- TT.IdsCursorKey,- TT.UsersCursorKey,- TT.ListsCursorKey, ) where import Control.Lens
Web/Twitter/Conduit/Request/Internal.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-}@@ -96,7 +95,6 @@ replace k Nothing = dropAssoc k dropAssoc k = filter ((/= k) . fst) -{- ORMOLU_DISABLE -} instance ( Parameters req , ParameterValue a@@ -105,13 +103,8 @@ , Functor f , lens ~ ((Maybe a -> f (Maybe a)) -> req -> f req) ) =>- IsLabel label lens where--#if MIN_VERSION_base(4, 10, 0)+ IsLabel label lens+ where fromLabel = rawParam key-#else- fromLabel _ = rawParam key-#endif where key = S8.pack (symbolVal (Proxy :: Proxy label))-{- ORMOLU_ENABLE -}
Web/Twitter/Conduit/Status.hs view
@@ -1,323 +1,75 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeOperators #-}- module Web.Twitter.Conduit.Status (+ -- * Notice+ -- $notice+ -- * Timelines- StatusesMentionsTimeline, mentionsTimeline,- StatusesUserTimeline, userTimeline,- StatusesHomeTimeline, homeTimeline,- StatusesRetweetsOfMe, retweetsOfMe, -- * Tweets- StatusesRetweetsId, retweetsId,- StatusesShowId, showId,- StatusesDestroyId, destroyId,- StatusesUpdate, update,- StatusesRetweetId, retweetId,- MediaData (..),- StatusesUpdateWithMedia, updateWithMedia,- -- , oembed- -- , retweetersIds- StatusesLookup, lookup, ) where -import Web.Twitter.Conduit.Base+import Data.Text (Text)+import Web.Twitter.Conduit.Api import Web.Twitter.Conduit.Parameters import Web.Twitter.Conduit.Request-import Web.Twitter.Conduit.Request.Internal import Web.Twitter.Types-import Prelude hiding (lookup) -import Data.Default-import qualified Data.Text as T-import Network.HTTP.Client.MultipartFormData---- $setup--- >>> :set -XOverloadedStrings -XOverloadedLabels--- >>> import Control.Lens---- * Timelines+import Prelude hiding (lookup) --- | Returns query data asks the most recent mentions for the authenticating user.------ You can perform a query using 'call':------ @--- res <- 'call' 'mentionsTimeline'--- @+-- $notice ----- >>> mentionsTimeline--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/mentions_timeline.json" []-mentionsTimeline :: APIRequest StatusesMentionsTimeline [Status]-mentionsTimeline = APIRequest "GET" (endpoint ++ "statuses/mentions_timeline.json") def+-- This module provides aliases of statuses API, for backward compatibility. -type StatusesMentionsTimeline =- '[ "count" ':= Integer- , "since_id" ':= Integer- , "max_id" ':= Integer- , "trim_user" ':= Bool- , "contributor_details" ':= Bool- , "include_entities" ':= Bool- , "tweet_mode" ':= TweetMode- ]+mentionsTimeline :: APIRequest StatusesMentionsTimeline [Status]+mentionsTimeline = statusesMentionsTimeline+{-# DEPRECATED mentionsTimeline "Please use Web.Twitter.Conduit.API.statusesMentionsTimeline" #-} --- | Returns query data asks a collection of the most recent Tweets posted by the user indicated by the screen_name or user_id parameters.------ You can perform a search query using 'call':------ @--- res <- 'call' $ 'userTimeline' ('ScreenNameParam' \"thimura\")--- @------ >>> userTimeline (ScreenNameParam "thimura")--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/user_timeline.json" [("screen_name","thimura")]--- >>> userTimeline (ScreenNameParam "thimura") & #include_rts ?~ True & #count ?~ 200--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/user_timeline.json" [("count","200"),("include_rts","true"),("screen_name","thimura")] userTimeline :: UserParam -> APIRequest StatusesUserTimeline [Status]-userTimeline q = APIRequest "GET" (endpoint ++ "statuses/user_timeline.json") (mkUserParam q)--type StatusesUserTimeline =- '[ "count" ':= Integer- , "since_id" ':= Integer- , "max_id" ':= Integer- , "trim_user" ':= Bool- , "exclude_replies" ':= Bool- , "contributor_details" ':= Bool- , "include_rts" ':= Bool- , "tweet_mode" ':= TweetMode- ]+userTimeline = statusesUserTimeline+{-# DEPRECATED userTimeline "Please use Web.Twitter.Conduit.API.statusesUserTimeline" #-} --- | Returns query data asks a collection of the most recentTweets and retweets posted by the authenticating user and the users they follow.------ You can perform a search query using 'call':------ @--- res <- 'call' 'homeTimeline'--- @------ >>> homeTimeline--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/home_timeline.json" []--- >>> homeTimeline & #count ?~ 200--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/home_timeline.json" [("count","200")] homeTimeline :: APIRequest StatusesHomeTimeline [Status]-homeTimeline = APIRequest "GET" (endpoint ++ "statuses/home_timeline.json") def--type StatusesHomeTimeline =- '[ "count" ':= Integer- , "since_id" ':= Integer- , "max_id" ':= Integer- , "trim_user" ':= Bool- , "exclude_replies" ':= Bool- , "contributor_details" ':= Bool- , "include_entities" ':= Bool- , "tweet_mode" ':= TweetMode- ]+homeTimeline = statusesHomeTimeline+{-# DEPRECATED homeTimeline "Please use Web.Twitter.Conduit.API.statusesHomeTimeline" #-} --- | Returns query data asks the most recent tweets authored by the authenticating user that have been retweeted by others.------ You can perform a search query using 'call':------ @--- res <- 'call' 'retweetsOfMe'--- @------ >>> retweetsOfMe--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets_of_me.json" []--- >>> retweetsOfMe & #count ?~ 100--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets_of_me.json" [("count","100")] retweetsOfMe :: APIRequest StatusesRetweetsOfMe [Status]-retweetsOfMe = APIRequest "GET" (endpoint ++ "statuses/retweets_of_me.json") def--type StatusesRetweetsOfMe =- '[ "count" ':= Integer- , "since_id" ':= Integer- , "max_id" ':= Integer- , "trim_user" ':= Bool- , "include_entities" ':= Bool- , "include_user_entities" ':= Bool- , "tweet_mode" ':= TweetMode- ]---- * Tweets+retweetsOfMe = statusesRetweetsOfMe+{-# DEPRECATED retweetsOfMe "Please use Web.Twitter.Conduit.API.statusesRetweetsOfMe" #-} --- | Returns query data that asks for the most recent retweets of the specified tweet------ You can perform a search query using 'call':------ @--- res <- 'call' twInfo mgr '$' 'retweetsId' 1234567890--- @------ >>> retweetsId 1234567890--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets/1234567890.json" []--- >>> retweetsId 1234567890 & #count ?~ 100--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/retweets/1234567890.json" [("count","100")] retweetsId :: StatusId -> APIRequest StatusesRetweetsId [RetweetedStatus]-retweetsId status_id = APIRequest "GET" uri def- where- uri = endpoint ++ "statuses/retweets/" ++ show status_id ++ ".json"--type StatusesRetweetsId =- '[ "count" ':= Integer- , "trim_user" ':= Bool- , "tweet_mode" ':= TweetMode- ]+retweetsId = statusesRetweetsId+{-# DEPRECATED retweetsId "Please use Web.Twitter.Conduit.API.statusesRetweetsId" #-} --- | Returns query data asks a single Tweet, specified by the id parameter.------ You can perform a search query using 'call':------ @--- res <- 'call' twInfo mgr '$' 'showId' 1234567890--- @------ >>> showId 1234567890--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/show/1234567890.json" []--- >>> showId 1234567890 & #include_my_retweet ?~ True--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/show/1234567890.json" [("include_my_retweet","true")] showId :: StatusId -> APIRequest StatusesShowId Status-showId status_id = APIRequest "GET" uri def- where- uri = endpoint ++ "statuses/show/" ++ show status_id ++ ".json"--type StatusesShowId =- '[ "trim_user" ':= Bool- , "include_my_retweet" ':= Bool- , "include_entities" ':= Bool- , "include_ext_alt_text" ':= Bool- , "tweet_mode" ':= TweetMode- ]+showId = statusesShowId+{-# DEPRECATED showId "Please use Web.Twitter.Conduit.API.statusesShowId" #-} --- | Returns post data which destroys the status specified by the require ID parameter.------ You can perform a search query using 'call':------ @--- res <- 'call' twInfo mgr '$' 'destroyId' 1234567890--- @------ >>> destroyId 1234567890--- APIRequest "POST" "https://api.twitter.com/1.1/statuses/destroy/1234567890.json" [] destroyId :: StatusId -> APIRequest StatusesDestroyId Status-destroyId status_id = APIRequest "POST" uri def- where- uri = endpoint ++ "statuses/destroy/" ++ show status_id ++ ".json"--type StatusesDestroyId =- '[ "trim_user" ':= Bool- , "tweet_mode" ':= 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\"--- @------ >>> update "Hello World"--- APIRequest "POST" "https://api.twitter.com/1.1/statuses/update.json" [("status","Hello World")]--- >>> update "Hello World" & #in_reply_to_status_id ?~ 1234567890--- APIRequest "POST" "https://api.twitter.com/1.1/statuses/update.json" [("in_reply_to_status_id","1234567890"),("status","Hello World")]-update :: T.Text -> APIRequest StatusesUpdate Status-update status = APIRequest "POST" uri [("status", PVString status)]- where- uri = endpoint ++ "statuses/update.json"+destroyId = statusesDestroyId+{-# DEPRECATED destroyId "Please use Web.Twitter.Conduit.API.statusesDestroyId" #-} -type StatusesUpdate =- '[ "in_reply_to_status_id" ':= Integer- , -- , "lat_long"- -- , "place_id"- "display_coordinates" ':= Bool- , "trim_user" ':= Bool- , "media_ids" ':= [Integer]- , "tweet_mode" ':= TweetMode- ]+update :: Text -> APIRequest StatusesUpdate Status+update = statusesUpdate+{-# DEPRECATED update "Please use Web.Twitter.Conduit.API.statusesUpdate" #-} --- | Returns post data which retweets a tweet, specified by ID.------ You can perform a search query using 'call':------ @--- res <- 'call' twInfo mgr '$' 'retweetId' 1234567890--- @------ >>> retweetId 1234567890--- APIRequest "POST" "https://api.twitter.com/1.1/statuses/retweet/1234567890.json" [] retweetId :: StatusId -> APIRequest StatusesRetweetId RetweetedStatus-retweetId status_id = APIRequest "POST" uri def- where- uri = endpoint ++ "statuses/retweet/" ++ show status_id ++ ".json"--type StatusesRetweetId =- '[ "trim_user" ':= Bool- , "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\")--- @------ >>> updateWithMedia "Hello World" (MediaFromFile "/home/fuga/test.jpeg")--- APIRequestMultipart "POST" "https://api.twitter.com/1.1/statuses/update_with_media.json" [("status","Hello World")]-updateWithMedia ::- T.Text ->- MediaData ->- APIRequest StatusesUpdateWithMedia Status-updateWithMedia tweet mediaData =- APIRequestMultipart "POST" uri [("status", PVString tweet)] [mediaBody mediaData]- where- uri = endpoint ++ "statuses/update_with_media.json"- mediaBody (MediaFromFile fp) = partFileSource "media[]" fp- mediaBody (MediaRequestBody filename filebody) = partFileRequestBody "media[]" filename filebody+retweetId = statusesRetweetId+{-# DEPRECATED retweetId "Please use Web.Twitter.Conduit.API.statusesRetweetId" #-} -type StatusesUpdateWithMedia =- '[ "possibly_sensitive" ':= Bool- , "in_reply_to_status_id" ':= Integer- , -- , "lat_long"- -- , "place_id"- "display_coordinates" ':= Bool- , "tweet_mode" ':= TweetMode- ]+updateWithMedia :: Text -> MediaData -> APIRequest StatusesUpdateWithMedia Status+updateWithMedia = statusesUpdateWithMedia+{-# DEPRECATED updateWithMedia "Please use Web.Twitter.Conduit.API.statusesUpdateWithMedia" #-} --- | Returns fully-hydrated tweet objects for up to 100 tweets per request, as specified by comma-separated values passed to the id parameter.------ You can perform a request using 'call':------ @--- res <- 'call' twInfo mgr '$' 'lookup' [20, 432656548536401920]--- @------ >>> lookup [10]--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/lookup.json" [("id","10")]--- >>> lookup [10, 432656548536401920]--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/lookup.json" [("id","10,432656548536401920")]--- >>> lookup [10, 432656548536401920] & #include_entities ?~ True--- APIRequest "GET" "https://api.twitter.com/1.1/statuses/lookup.json" [("include_entities","true"),("id","10,432656548536401920")] lookup :: [StatusId] -> APIRequest StatusesLookup [Status]-lookup ids = APIRequest "GET" (endpoint ++ "statuses/lookup.json") [("id", PVIntegerArray ids)]--type StatusesLookup =- '[ "include_entities" ':= Bool- , "trim_user" ':= Bool- , "map" ':= Bool- , "tweet_mode" ':= TweetMode- ]+lookup = statusesLookup+{-# DEPRECATED lookup "Please use Web.Twitter.Conduit.API.statusesLookup" #-}
tests/ApiSpec.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} module ApiSpec where@@ -6,17 +7,17 @@ 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, call, sourceWithCursor)+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 -import Test.Hspec- twInfo :: TWInfo twInfo = unsafePerformIO getTWInfo @@ -24,6 +25,10 @@ mgr = unsafePerformIO $ newManager tlsManagerSettings {-# NOINLINE mgr #-} +self :: User+self = unsafePerformIO $ call twInfo mgr $ accountVerifyCredentials+{-# NOINLINE self #-}+ spec :: Spec spec = do unit@@ -37,6 +42,70 @@ 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")@@ -44,7 +113,7 @@ it "iterate with sourceWithCursor" $ do let src = sourceWithCursor twInfo mgr $ friendsIds (Param.ScreenNameParam "thimura")- friends <- src $$ CL.consume+ friends <- runConduit $ src .| CL.consume length friends `shouldSatisfy` (>= 0) describe "listsMembers" $ do@@ -58,5 +127,5 @@ it "iterate with sourceWithCursor" $ do let src = sourceWithCursor twInfo mgr $ listsMembers (Param.ListNameParam "thimura/haskell")- members <- src $$ CL.consume+ members <- runConduit $ src .| CL.consume members ^.. traversed . userScreenName `shouldContain` ["Hackage"]
− tests/StatusSpec.hs
@@ -1,107 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedLabels #-}-{-# LANGUAGE OverloadedStrings #-}--module StatusSpec where--import Control.Lens-import Data.Conduit-import qualified Data.Conduit.List as CL-import Data.Time-import Network.HTTP.Conduit-import System.IO.Unsafe-import Web.Twitter.Conduit (TWInfo, accountVerifyCredentials, call, sourceWithMaxId)-import qualified Web.Twitter.Conduit.Parameters as Param-import Web.Twitter.Conduit.Status as Status-import Web.Twitter.Types.Lens--import Test.Hspec-import TestUtils--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 mentionsTimeline- length res `shouldSatisfy` (> 0)- let mentionsScreenName = res ^.. traversed . statusEntities . _Just . enUserMentions . traversed . entityBody . userEntityUserScreenName- mentionsScreenName `shouldSatisfy` allOf folded (== (self ^. userScreenName))- length mentionsScreenName `shouldSatisfy` (== length res)-- describe "userTimeline" $ do- it "returns the 20 most recent tweets posted by the user indicated by ScreenNameParam" $ do- res <- call twInfo mgr $ userTimeline (Param.ScreenNameParam "thimura")- length res `shouldSatisfy` (== 20)- res `shouldSatisfy` (allOf folded (^. statusUser . userScreenName . to (== "thimura")))- it "returns the recent tweets which include RTs when specified include_rts option" $ do- res <-- call twInfo mgr $- userTimeline (Param.ScreenNameParam "thimura")- & #count ?~ 100- & #include_rts ?~ True- res `shouldSatisfy` (anyOf (folded . statusRetweetedStatus . _Just . statusUser . userScreenName) (/= "thimura"))- it "iterate with sourceWithMaxId" $ do- let src =- sourceWithMaxId twInfo mgr $- userTimeline (Param.ScreenNameParam "thimura") & #count ?~ 200- tl <- src $$ CL.isolate 600 =$ CL.consume- length tl `shouldSatisfy` (== 600)-- let ids = tl ^.. traversed . statusId- zip ids (tail ids) `shouldSatisfy` all (\(a, b) -> a > b)-- describe "homeTimeline" $ do- it "returns the most recent tweets in home timeline" $ do- res <- call twInfo mgr homeTimeline- length res `shouldSatisfy` (> 0)-- describe "showId" $ do- it "works for the known tweets" $ do- res <- call twInfo mgr $ showId 477833886768959488- res ^. statusId `shouldBe` 477833886768959488- res ^. statusText `shouldBe` "真紅かわいいはアレセイア"- res ^. statusCreatedAt `shouldBe` UTCTime (fromGregorian 2014 6 14) (secondsToDiffTime 55450)- res ^. statusUser . userScreenName `shouldBe` "thimura"-- describe "update & destroyId" $ do- it "posts new tweet and destroy it" $ do- res1 <- call twInfo mgr $ update "おまえの明日が、今日よりもずっと、楽しい事で溢れているようにと、祈っているよ"- res1 ^. statusUser . userScreenName `shouldBe` self ^. userScreenName-- res2 <- call twInfo mgr $ destroyId (res1 ^. statusId)- res2 ^. statusId `shouldBe` res1 ^. statusId-- describe "lookup" $ do- it "works for the known tweets" $ do- res <- call twInfo mgr $ Status.lookup [438691466345340928, 477757405942411265]- length res `shouldSatisfy` (== 2)- (res !! 0) ^. statusId `shouldBe` 438691466345340928- (res !! 1) ^. statusId `shouldBe` 477757405942411265-- it "handles extended tweets" $ do- res <- call twInfo mgr $ Status.lookup [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 $ Status.lookup [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"
twitter-conduit.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.24 name: twitter-conduit-version: 0.6.0+version: 0.6.1 license: BSD3 license-file: LICENSE maintainer: Takahiro HIMURA <taka@himura.jp>@@ -66,7 +66,7 @@ default-language: Haskell2010 ghc-options: -Wall build-depends:- base >=4.9 && <5,+ base >=4.12 && <5, aeson >=0.7.0.5, attoparsec >=0.10, authenticate-oauth >=1.3,@@ -75,6 +75,7 @@ conduit-extra >=1.3, containers, data-default >=0.3,+ deepseq, exceptions >=0.5, ghc-prim, http-client >=0.5.0,@@ -107,7 +108,6 @@ Spec ApiSpec BaseSpec- StatusSpec TestUtils default-language: Haskell2010