packages feed

twitter-conduit 0.0.5.8 → 0.6.1

raw patch · 47 files changed

Files

.gitignore view
@@ -7,12 +7,16 @@ .DS_Store .gdb_history TAGS-# Object files+# Object files, etc *.a *.o *.so *.hi *.p_hi+*.chi+*.chs.h+*.tix+.hpc a.out # autotool autom4te.cache@@ -22,5 +26,8 @@ Main dist/ cabal-dev/+.cabal-sandbox/ cabal.sandbox.config-.cabal-sandbox+dist-newstyle/+.ghc.environment.*+.stack-work
− .travis.yml
@@ -1,64 +0,0 @@-language: haskell--env:-  global:-    # OAUTH_CONSUMER_KEY-    - secure: "U3hh6wJf87tdZmwLtPyMQ0ci39ouKvTm/JCi0CCz2jaPvWZR+4Tt7aYV5WYqzjSmgfBOqKpXTpwFBcvvWRty8rI1b8OtrlljC1zUcgf/hsz3hfPtv+GKW/ldYnqg7znyeAdxWEF1a4JszKgOJaGMdX7zSpFewgwEq7T4jAlebLA="-    # OAUTH_CONSUMER_SECRET-    - secure: "I4BGd16zNQE3WjnwgdT7OM5sIIMtT9DMFPNdnWJPcgYndDovnFAUDoTomaGo0Wue4h64vGWl26f2Nm04i2upJXxkzFFtU5Az6cwUjzkOve1yqrkkyPFTXC8MLQYX3Zz8scQ8h1IJCn0Luu5HsHsool6IFZpiUovLZbDXmY44yH4="-    # OAUTH_ACCESS_TOKEN-    - secure: "dW2pYRplxJ7HmJEo3lMgLlzfdOylBvWiV4PJt1j4GMDPJBOu3RM0h3zNkkxDoS9D0wWRkBEH0iN2Bh08v2z7sJtF3f/kgLvPlOy5mNSYcozwMdHJRlJEcXaGULHbZ4p79lVIOdTAhY2WU2QdP7PEv0BiqqVcysnfK0TGkTa+KEc="-    # OAUTH_ACCESS_SECRET-    - secure: "I5hzi7sONDn8KJAhmkroHsGNJ2zUxYq2iGamTV9lgMUDH0bwpMx+guzfZr+fGesdlUphM0t+ZCh9YH8BoVJsjZ/db9u/lotg78DKp1QwhrWnISqWj6TIfXrSLPkBwY+xFtxl6Ihsjyl+ileiK5sb0C9mC+zXidgBqEM73miAokY="-  matrix:-    - GHCVER=7.4.2-    - GHCVER=7.6.3-    - GHCVER=7.8.3 USE_COVERALLS=1 CABAL_FLAGS="-frun-integrated-test"-    - GHCVER=head--matrix:-  allow_failures:-    - env: GHCVER=7.4.2-    - env: GHCVER=head--before_install:-  - |-    if [ $GHCVER = `ghc --numeric-version` ]; then-      echo "use system ghc: `which ghc` `ghc --numeric-version`"-    else-      travis_retry sudo add-apt-repository -y ppa:hvr/ghc-      travis_retry sudo apt-get update-      travis_retry sudo apt-get install  -y --force-yes cabal-install-1.18 ghc-$GHCVER-      export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/1.18/bin:$PATH-      echo "use ppa:hvr/ghc: `which ghc` `ghc --numeric-version`"-    fi--install:-  - export PATH=$HOME/.cabal/bin:$PATH-  - which cabal-  - travis_retry cabal update-  # GHC 7.8.3 exports transformers-0.3.0, but happy and our package installs transformers 0.4 series.-  # However, doctest fails when we have multiple version of transformers.-  # To prevent this, we restrict transformers version to (< 0.4)-  - cabal install happy --constraint="transformers < 0.4"-  - cabal install --only-dependencies --enable-tests-  - "[ -n \"$USE_COVERALLS\" ] && travis_retry cabal install hpc-coveralls || true"-  - ghc-pkg list--script:-  - |-    if [ -n "$USE_COVERALLS" ]; then-      cabal configure --enable-tests --enable-library-coverage $CABAL_FLAGS-    else-      cabal configure --enable-tests $CABAL_FLAGS-    fi-  - cabal build-  - |-    if [ -n "$USE_COVERALLS" ]; then-      run-cabal-test --show-details=always-    else-      cabal test --show-details=always-    fi--after_script:-  - "[ -n \"$USE_COVERALLS\" ] && hpc-coveralls --exclude-dir=tests spec_main || true"
+ ChangeLog.md view
@@ -0,0 +1,98 @@+## 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'`
README.md view
@@ -1,6 +1,8 @@ # twitter-conduit: An Conduit based Twitter API library for Haskell # -[![Build Status](https://travis-ci.org/himura/twitter-conduit.png?branch=master)](https://travis-ci.org/himura/twitter-conduit)+![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 ## @@ -8,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).@@ -22,19 +19,33 @@  ### 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 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 ###  First, you must obtain consumer key and secret from [Twitter Application Management](https://apps.twitter.com/) page,@@ -62,7 +73,7 @@  and open http://localhost:3000/signIn in your browser. -In both cases, you can obtain OAUTH_ACCESSS_TOKEN and OAUTH_ACCESS_SECRET variables,+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:  ~~~~
Setup.hs view
@@ -1,3 +1,26 @@-#!/usr/bin/env runhaskell+{-# 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
+ Warning.hs view
@@ -0,0 +1,8 @@+module 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
Web/Twitter/Conduit.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} {-# OPTIONS_GHC -fno-warn-unused-imports #-}  -- |@@ -10,56 +9,81 @@ -- Portability: portable -- -- A client library for Twitter APIs (see <https://dev.twitter.com/>).+module Web.Twitter.Conduit (+    -- * How to use this library+    -- $howto -module Web.Twitter.Conduit-       (-       -- * How to use this library-       -- $howto+    -- ** Authentication+    -- $auth -       -- * Re-exports-         module Web.Twitter.Conduit.Base-       , module Web.Twitter.Conduit.Api-       , module Web.Twitter.Conduit.Status-       , module Web.Twitter.Conduit.Monad-       , module Web.Twitter.Conduit.Stream-       , module Web.Twitter.Conduit.Request-       , module Web.Twitter.Conduit.Parameters+    -- ** How to call API+    -- $call -       , MediaData (..)-#if !MIN_VERSION_twitter_types(0,5,0)-       , UploadedMedia-       , mediaId-       , mediaSize-       , mediaImage-       , ImageSizeType-       , imageWidth-       , imageHeight-       , imageType-#endif-       ) where+    -- *** How to specify API parameters+    -- $parameter -import Web.Twitter.Conduit.Types-import Web.Twitter.Conduit.Base+    -- *** 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.Status-import Web.Twitter.Conduit.Monad-import Web.Twitter.Conduit.Stream-import Web.Twitter.Conduit.Request+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 --- for haddock+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-import Control.Monad.Logger-import Control.Monad.IO.Class-import Control.Lens -#ifdef HLINT {-# ANN module "HLint: ignore Use import/export shortcut" #-}-#endif  -- $howto --@@ -69,18 +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.Authenticate.OAuth--- > 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.Logger--- > 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: --@@ -122,17 +150,19 @@ -- -- Or, simply as follows: ----- > twInfo = setCredential tokens credential def------ Every twitter API functions are run in the 'TW' monad.--- By using 'runTW' function, you can perform computation in 'TW' monad.+-- @+-- 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> -- could be obtained by: -- -- @--- timeline <- 'call' 'homeTimeline'+-- mgr \<- 'newManager' 'tlsManagerSettings'+-- timeline \<- 'call' twInfo mgr 'statusesHomeTimeline' -- @ -- -- The response of 'call' function is wrapped by the suitable type of@@ -145,9 +175,48 @@ -- includes 20 tweets, and you can change the number of tweets by the /count/ parameter. -- -- @--- timeline <- 'call' '$' '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,@@ -155,23 +224,31 @@ -- or use the conduit wrapper 'sourceWithCursor' as below: -- -- @--- friends <- 'sourceWithCursor' ('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 = runNoLoggingT . runTW twInfo $ do---     'sourceWithMaxId' 'homeTimeline'---         $= CL.isolate 60---         $$ CL.mapM_ $ \status -> liftIO $ do---             T.putStrLn $ T.concat [ T.pack . show $ status ^. statusId---                                   , \": \"---                                   , status ^. statusUser . userScreenName---                                   , \": \"---                                   , status ^. statusText---                                   ]+-- 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+--                          ]+--             ) -- @
Web/Twitter/Conduit/Api.hs view
@@ -1,649 +1,1297 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE EmptyDataDecls #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE CPP #-}-#if __GLASGOW_HASKELL__ >= 704-{-# LANGUAGE ConstraintKinds #-}-#endif--module Web.Twitter.Conduit.Api-       (-       -- * Search-         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-       -- , 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-       -- , listsMembersDestroy-       -- , listsMemberships-       -- , listsSubscribers-       -- , listsSubscribersCreate-       -- , listsSubscribersShow-       -- , listsSubscribersDestroy-       -- , listsSubscribersCreateAll-       -- , listsMembersShow-       , ListsMembers-       , listsMembers-       -- , listsMembersCreate-       -- , listsDestroy-       -- , listsUpdate-       -- , listsCreate-       -- , listsShow-       -- , listsSubscriptions-       -- , listsMembersDestroyAll-       -- , listsOwnerships--       -- * Saved Searches-       -- savedSearchesList-       -- savedSearchesShowId-       -- savedSearchesCreate-       -- savedSearchesDestroyId--       -- * Places & Geo-       -- geoIdPlaceId-       -- geoReverseGeocode-       -- geoSearch-       -- geoSimilarPlaces-       -- geoPlace--       -- * media-       , MediaUpload-       , mediaUpload-       ) where--import Web.Twitter.Types-import Web.Twitter.Conduit.Types-import Web.Twitter.Conduit.Parameters-import Web.Twitter.Conduit.Parameters.TH-import Web.Twitter.Conduit.Base-import Web.Twitter.Conduit.Request--import Network.HTTP.Client.MultipartFormData-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import Data.Default---- $setup--- >>> :set -XOverloadedStrings--- >>> import Control.Lens--data SearchTweets--- | Returns search query.------ You can perform a search query using 'call':------ @--- res <- 'call' ('searchTweets' \"search text\")--- 'liftIO' . 'print' $ res ^. 'searchResultStatuses'--- @------ >>> searchTweets "search text"--- APIRequestGet "https://api.twitter.com/1.1/search/tweets.json" [("q","search text")]--- >>> searchTweets "search text" & lang ?~ "ja" & count ?~ 100--- APIRequestGet "https://api.twitter.com/1.1/search/tweets.json" [("count","100"),("lang","ja"),("q","search text")]-searchTweets :: T.Text -- ^ search string-             -> APIRequest SearchTweets (SearchResult [SearchStatus])-searchTweets q = APIRequestGet (endpoint ++ "search/tweets.json") [("q", T.encodeUtf8 q)]-deriveHasParamInstances ''SearchTweets-    [ "lang"-    , "locale"-    -- , "result_type"-    , "count"-    , "until"-    , "since_id"-    , "max_id"-    , "include_entities"-    -- , "callback"  (needless)-    ]---- | Alias of 'searchTweets', for backward compatibility-search :: T.Text -- ^ search string-       -> APIRequest SearchTweets (SearchResult [SearchStatus])-search = searchTweets--data DirectMessages--- | Returns query data which asks recent direct messages sent to the authenticating user.------ You can perform a query using 'call':------ @--- res <- 'call' '$' 'directMessages' '&' 'count' '?~' 100--- @------ >>> directMessages--- APIRequestGet "https://api.twitter.com/1.1/direct_messages.json" []--- >>> directMessages & count ?~ 100--- APIRequestGet "https://api.twitter.com/1.1/direct_messages.json" [("count","100")]-directMessages :: APIRequest DirectMessages [DirectMessage]-directMessages = APIRequestGet (endpoint ++ "direct_messages.json") def-deriveHasParamInstances ''DirectMessages-    [ "since_id"-    , "max_id"-    , "count"-    , "include_entities"-    , "skip_status"-    ]--data DirectMessagesSent--- | Returns query data which asks recent direct messages sent by the authenticating user.------ You can perform a query using 'call':------ @--- res <- 'call' '$' 'directMessagesSent' '&' 'count' '?~' 100--- @------ >>> directMessagesSent--- APIRequestGet "https://api.twitter.com/1.1/direct_messages/sent.json" []--- >>> directMessagesSent & count ?~ 100--- APIRequestGet "https://api.twitter.com/1.1/direct_messages/sent.json" [("count","100")]-directMessagesSent :: APIRequest DirectMessagesSent [DirectMessage]-directMessagesSent = APIRequestGet (endpoint ++ "direct_messages/sent.json") def-deriveHasParamInstances ''DirectMessagesSent-    [ "since_id"-    , "max_id"-    , "count"-    , "include_entities"-    , "page"-    , "skip_status"-    ]--data DirectMessagesShow--- | Returns query data which asks a single direct message, specified by an id parameter.------ You can perform a query using 'call':------ @--- res <- 'call' '$' 'directMessagesShow' 1234567890--- @------ >>> directMessagesShow 1234567890--- APIRequestGet "https://api.twitter.com/1.1/direct_messages/show.json" [("id","1234567890")]-directMessagesShow :: StatusId -> APIRequest DirectMessagesShow DirectMessage-directMessagesShow sId = APIRequestGet (endpoint ++ "direct_messages/show.json") [("id", showBS sId)]--data DirectMessagesDestroy--- | Returns post data which destroys the direct message specified in the required ID parameter.------ You can perform a query using 'call':------ @--- res <- 'call' '$' 'directMessagesDestroy' 1234567890--- @------ >>> directMessagesDestroy 1234567890--- APIRequestPost "https://api.twitter.com/1.1/direct_messages/destroy.json" [("id","1234567890")]-directMessagesDestroy :: StatusId -> APIRequest DirectMessagesDestroy DirectMessage-directMessagesDestroy sId = APIRequestPost (endpoint ++ "direct_messages/destroy.json") [("id", showBS sId)]-deriveHasParamInstances ''DirectMessagesDestroy-    [ "include_entities"-    ]--data DirectMessagesNew--- | Returns post data which sends a new direct message to the specified user from the authenticating user.------ You can perform a post using 'call':------ @--- res <- 'call' '$' 'directMessagesNew' (ScreenNameParam \"thimura\") \"Hello DM\"--- @------ >>> directMessagesNew (ScreenNameParam "thimura") "Hello DM"--- APIRequestPost "https://api.twitter.com/1.1/direct_messages/new.json" [("text","Hello DM"),("screen_name","thimura")]--- >>> directMessagesNew (UserIdParam 69179963) "Hello thimura! by UserId"--- APIRequestPost "https://api.twitter.com/1.1/direct_messages/new.json" [("text","Hello thimura! by UserId"),("user_id","69179963")]-directMessagesNew :: UserParam -> T.Text -> APIRequest DirectMessagesNew DirectMessage-directMessagesNew q msg = APIRequestPost (endpoint ++ "direct_messages/new.json") (("text", T.encodeUtf8 msg):mkUserParam q)--data FriendshipsNoRetweetsIds--- | 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' '$' 'friendshipsNoRetweetsIds'--- @------ >>> friendshipsNoRetweetsIds--- APIRequestGet "https://api.twitter.com/1.1/friendships/no_retweets/ids.json" []-friendshipsNoRetweetsIds :: APIRequest FriendshipsNoRetweetsIds [UserId]-friendshipsNoRetweetsIds = APIRequestGet (endpoint ++ "friendships/no_retweets/ids.json") []--data FriendsIds--- | Returns query data which asks a collection of user IDs for every user the specified user is following.------ You can perform a query using 'call':------ @--- res <- 'call' '$' 'friendsIds' ('ScreenNameParam' \"thimura\")--- @------ Or, you can iterate with 'sourceWithCursor':------ @--- 'sourceWithCursor' ('friendsIds' ('ScreenNameParam' \"thimura\")) $$ CL.consume--- @------ >>> friendsIds (ScreenNameParam "thimura")--- APIRequestGet "https://api.twitter.com/1.1/friends/ids.json" [("screen_name","thimura")]--- >>> friendsIds (ScreenNameParam "thimura") & count ?~ 5000--- APIRequestGet "https://api.twitter.com/1.1/friends/ids.json" [("count","5000"),("screen_name","thimura")]-friendsIds :: UserParam -> APIRequest FriendsIds (WithCursor IdsCursorKey UserId)-friendsIds q = APIRequestGet (endpoint ++ "friends/ids.json") (mkUserParam q)-deriveHasParamInstances ''FriendsIds-    [ "cursor"-    -- , "stringify_ids" -- (needless)-    , "count"-    ]--data FollowersIds--- | Returns query data which asks a collection of user IDs for every user following the specified user.------ You can perform a query using 'call':------ @--- res <- 'call' '$' 'followersIds' ('ScreenNameParam' \"thimura\")--- @------ Or, you can iterate with 'sourceWithCursor':------ @--- 'sourceWithCursor' ('followersIds' ('ScreenNameParam' \"thimura\")) $$ CL.consume--- @------ >>> followersIds (ScreenNameParam "thimura")--- APIRequestGet "https://api.twitter.com/1.1/followers/ids.json" [("screen_name","thimura")]--- >>> followersIds (ScreenNameParam "thimura") & count ?~ 5000--- APIRequestGet "https://api.twitter.com/1.1/followers/ids.json" [("count","5000"),("screen_name","thimura")]-followersIds :: UserParam -> APIRequest FollowersIds (WithCursor IdsCursorKey UserId)-followersIds q = APIRequestGet (endpoint ++ "followers/ids.json") (mkUserParam q)-deriveHasParamInstances ''FollowersIds-    [ "cursor"-    -- , "stringify_ids" -- (needless)-    , "count"-    ]--data FriendshipsIncoming--- | 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' '$' 'friendshipsIncoming'--- @------ Or, you can iterate with 'sourceWithCursor':------ @--- 'sourceWithCursor' 'friendshipsIncoming' $$ CL.consume--- @------ >>> friendshipsIncoming--- APIRequestGet "https://api.twitter.com/1.1/friendships/incoming.json" []-friendshipsIncoming :: APIRequest FriendshipsIncoming (WithCursor IdsCursorKey UserId)-friendshipsIncoming = APIRequestGet (endpoint ++ "friendships/incoming.json") def-deriveHasParamInstances ''FriendshipsIncoming-    [ "cursor"-    -- , "stringify_ids" -- (needless)-    ]--data FriendshipsOutgoing--- | 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' '$' 'friendshipsOutgoing'--- @------ Or, you can iterate with 'sourceWithCursor':------ @--- 'sourceWithCursor' 'friendshipsOutgoing' $$ CL.consume--- @------ >>> friendshipsOutgoing--- APIRequestGet "https://api.twitter.com/1.1/friendships/outgoing.json" []-friendshipsOutgoing :: APIRequest FriendshipsOutgoing (WithCursor IdsCursorKey UserId)-friendshipsOutgoing = APIRequestGet (endpoint ++ "friendships/outgoing.json") def-deriveHasParamInstances ''FriendshipsOutgoing-    [ "cursor"-    -- , "stringify_ids" -- (needless)-    ]--data FriendshipsCreate--- | Returns post data which follows the user specified in the ID parameter.------ You can perform request by using 'call':------ @--- res <- 'call' '$' 'friendshipsCreate' ('ScreenNameParam' \"thimura\")--- @------ >>> friendshipsCreate (ScreenNameParam "thimura")--- APIRequestPost "https://api.twitter.com/1.1/friendships/create.json" [("screen_name","thimura")]--- >>> friendshipsCreate (UserIdParam 69179963)--- APIRequestPost "https://api.twitter.com/1.1/friendships/create.json" [("user_id","69179963")]-friendshipsCreate :: UserParam -> APIRequest FriendshipsCreate User-friendshipsCreate user = APIRequestPost (endpoint ++ "friendships/create.json") (mkUserParam user)-deriveHasParamInstances ''FriendshipsCreate-    [ "follow"-    ]--data FriendshipsDestroy--- | Returns post data which follows the user specified in the ID parameter.------ You can perform request by using 'call':------ @--- res <- 'call' '$' 'friendshipsDestroy' ('ScreenNameParam' \"thimura\")--- @------ >>> friendshipsDestroy (ScreenNameParam "thimura")--- APIRequestPost "https://api.twitter.com/1.1/friendships/destroy.json" [("screen_name","thimura")]--- >>> friendshipsDestroy (UserIdParam 69179963)--- APIRequestPost "https://api.twitter.com/1.1/friendships/destroy.json" [("user_id","69179963")]-friendshipsDestroy :: UserParam -> APIRequest FriendshipsDestroy User-friendshipsDestroy user = APIRequestPost (endpoint ++ "friendships/destroy.json") (mkUserParam user)--data FriendsList--- | Returns query data which asks a cursored collection of user objects for every user the specified users is following.------ You can perform request by using 'call':------ @--- res <- 'call' '$' 'friendsList' ('ScreenNameParam' \"thimura\")--- @------ Or, you can iterate with 'sourceWithCursor':------ @--- 'sourceWithCursor' ('friendsList' ('ScreenNameParam' \"thimura\")) $$ CL.consume--- @------ >>> friendsList (ScreenNameParam "thimura")--- APIRequestGet "https://api.twitter.com/1.1/friends/list.json" [("screen_name","thimura")]--- >>> friendsList (UserIdParam 69179963)--- APIRequestGet "https://api.twitter.com/1.1/friends/list.json" [("user_id","69179963")]-friendsList :: UserParam -> APIRequest FriendsList (WithCursor UsersCursorKey User)-friendsList q = APIRequestGet (endpoint ++ "friends/list.json") (mkUserParam q)-deriveHasParamInstances ''FriendsList-    [ "cursor"-    , "count"-    , "skip_status"-    , "include_user_entities"-    ]--data FollowersList--- | 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' '$' 'followersList' ('ScreenNameParam' \"thimura\")--- @------ Or, you can iterate with 'sourceWithCursor':------ @--- 'sourceWithCursor' ('followersList' ('ScreenNameParam' \"thimura\")) $$ CL.consume--- @------ >>> followersList (ScreenNameParam "thimura")--- APIRequestGet "https://api.twitter.com/1.1/followers/list.json" [("screen_name","thimura")]--- >>> followersList (UserIdParam 69179963)--- APIRequestGet "https://api.twitter.com/1.1/followers/list.json" [("user_id","69179963")]-followersList :: UserParam -> APIRequest FollowersList (WithCursor UsersCursorKey User)-followersList q = APIRequestGet (endpoint ++ "followers/list.json") (mkUserParam q)-deriveHasParamInstances ''FollowersList-    [ "cursor"-    , "count"-    , "skip_status"-    , "include_user_entities"-    ]--data AccountVerifyCredentials--- | Returns query data asks that the credential is valid.------ You can perform request by using 'call':------ @--- res <- 'call' '$' 'accountVerifyCredentials'--- @------ >>> accountVerifyCredentials--- APIRequestGet "https://api.twitter.com/1.1/account/verify_credentials.json" []-accountVerifyCredentials :: APIRequest AccountVerifyCredentials User-accountVerifyCredentials = APIRequestGet (endpoint ++ "account/verify_credentials.json") []-deriveHasParamInstances ''AccountVerifyCredentials-    [ "include_entities"-    , "skip_status"-    ]--data UsersLookup--- | Returns query data asks user objects.------ You can perform request by using 'call':------ @--- res <- 'call' '$' 'usersLookup' ('ScreenNameListParam' [\"thimura\", \"twitterapi\"])--- @------ >>> usersLookup (ScreenNameListParam ["thimura", "twitterapi"])--- APIRequestGet "https://api.twitter.com/1.1/users/lookup.json" [("screen_name","thimura,twitterapi")]-usersLookup :: UserListParam -> APIRequest UsersLookup [User]-usersLookup q = APIRequestGet (endpoint ++ "users/lookup.json") (mkUserListParam q)-deriveHasParamInstances ''UsersLookup-    [ "include_entities"-    ]--data UsersShow--- | Returns query data asks the user specified by user id or screen name parameter.------ You can perform request by using 'call':------ @--- res <- 'call' '$' 'usersShow' ('ScreenNameParam' \"thimura\")--- @------ >>> usersShow (ScreenNameParam "thimura")--- APIRequestGet "https://api.twitter.com/1.1/users/show.json" [("screen_name","thimura")]-usersShow :: UserParam -> APIRequest UsersShow User-usersShow q = APIRequestGet (endpoint ++ "users/show.json") (mkUserParam q)-deriveHasParamInstances ''UsersShow-    [ "include_entities"-    ]--data FavoritesList--- | Returns the 20 most recent Tweets favorited by the specified user.------ You can perform request by using 'call':------ @--- res <- 'call' '$' 'favoritesList' (ScreenNameParam \"thimura\")--- @------ >>> favoritesList Nothing--- APIRequestGet "https://api.twitter.com/1.1/favorites/list.json" []--- >>> favoritesList (Just (ScreenNameParam "thimura"))--- APIRequestGet "https://api.twitter.com/1.1/favorites/list.json" [("screen_name","thimura")]--- >>> favoritesList (Just (UserIdParam 69179963))--- APIRequestGet "https://api.twitter.com/1.1/favorites/list.json" [("user_id","69179963")]-favoritesList :: Maybe UserParam -> APIRequest FavoritesList [Status]-favoritesList mbuser = APIRequestGet (endpoint ++ "favorites/list.json") (mkParam mbuser)-  where-    mkParam Nothing = []-    mkParam (Just usr) = mkUserParam usr-deriveHasParamInstances ''FavoritesList-    [ "count"-    , "since_id"-    , "max_id"-    , "include_entities"-    ]--data FavoritesCreate--- | Returns post data which favorites the status specified in the ID parameter as the authenticating user.------ You can perform request by using 'call':------ @--- res <- 'call' '$' 'favoritesCreate' 1234567890--- @------ >>> favoritesCreate 1234567890--- APIRequestPost "https://api.twitter.com/1.1/favorites/create.json" [("id","1234567890")]-favoritesCreate :: StatusId -> APIRequest FavoritesCreate Status-favoritesCreate sid = APIRequestPost (endpoint ++ "favorites/create.json") [("id", showBS sid)]-deriveHasParamInstances ''FavoritesCreate-    [ "include_entities"-    ]--data FavoritesDestroy--- | Returns post data unfavorites the status specified in the ID paramter as the authenticating user.------ You can perform request by using 'call':------ @--- res <- 'call' '$' 'favoritesDestroy' 1234567890--- @------ >>> favoritesDestroy 1234567890--- APIRequestPost "https://api.twitter.com/1.1/favorites/destroy.json" [("id","1234567890")]-favoritesDestroy :: StatusId -> APIRequest FavoritesDestroy Status-favoritesDestroy sid = APIRequestPost (endpoint ++ "favorites/destroy.json") [("id", showBS sid)]-deriveHasParamInstances ''FavoritesDestroy-    [ "include_entities"-    ]--data ListsMembers--- | Returns query data asks the members of the specified list.------ You can perform request by using 'call':------ @--- res <- 'call' '$' 'listsMembers' ('ListNameParam' "thimura/haskell")--- @------ >>> listsMembers (ListNameParam "thimura/haskell")--- APIRequestGet "https://api.twitter.com/1.1/lists/members.json" [("slug","haskell"),("owner_screen_name","thimura")]--- >>> listsMembers (ListIdParam 20849097)--- APIRequestGet "https://api.twitter.com/1.1/lists/members.json" [("list_id","20849097")]-listsMembers :: ListParam -> APIRequest ListsMembers (WithCursor UsersCursorKey User)-listsMembers q = APIRequestGet (endpoint ++ "lists/members.json") (mkListParam q)-deriveHasParamInstances ''ListsMembers-    [ "cursor"-    , "skip_status"-    ]--data MediaUpload--- | 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' '$' 'mediaUpload' ('MediaFromFile' \"\/path\/to\/upload\/file1.png\")--- res2 <- 'call' '$' 'mediaUpload' ('MediaRequestBody' \"file2.png\" \"[.. file body ..]\")--- @------ and then collect the resulting media IDs and update your status by calling 'update':------ @--- 'call' '$' 'update' \"Hello World\" '&' 'mediaIds' '?~' ['mediaId' res1, 'mediaId' res2]--- @------ See: <https://dev.twitter.com/docs/api/multiple-media-extended-entities>------ >>> mediaUpload (MediaFromFile "/home/test/test.png")--- APIRequestPostMultipart "https://upload.twitter.com/1.1/media/upload.json" []-mediaUpload :: MediaData-            -> APIRequest MediaUpload UploadedMedia-mediaUpload mediaData =-    APIRequestPostMultipart 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+{-# 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: <https://dev.twitter.com/docs/api/multiple-media-extended-entities>+--+-- >>> 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
Web/Twitter/Conduit/Base.hs view
@@ -1,126 +1,149 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-} -module Web.Twitter.Conduit.Base-       ( api-       , getResponse-       , call-       , call'-       , checkResponse-       , sourceWithMaxId-       , sourceWithMaxId'-       , sourceWithCursor-       , sourceWithCursor'-       , TwitterBaseM-       , endpoint-       , makeRequest-       , sinkJSON-       , sinkFromJSON-       , showBS-       ) where+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.Monad-import Web.Twitter.Conduit.Types-import Web.Twitter.Conduit.Parameters+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 qualified Network.HTTP.Conduit as HTTP-import Network.HTTP.Client.MultipartFormData-import qualified Network.HTTP.Types as HT-import qualified Data.Conduit as C-import qualified Data.Conduit.List as CL-+import 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.Text as T+import qualified Data.Conduit.List as CL+import qualified Data.Map as M import qualified Data.Text.Encoding as T-import Data.ByteString (ByteString)-import qualified Data.ByteString.Char8 as S8-import Control.Monad.IO.Class-import Control.Monad.Trans.Class (lift)-import Control.Monad.Trans.Resource (MonadResource, MonadThrow, monadThrow)-import Control.Monad.Logger-import Control.Lens-import Unsafe.Coerce--type TwitterBaseM m = ( MonadResource m-                      , MonadLogger m-                      )+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 :: (MonadThrow m, MonadIO m)-            => APIRequest apiName responseType-            -> m HTTP.Request-makeRequest (APIRequestGet u pa) = makeRequest' "GET" u pa-makeRequest (APIRequestPost u pa) = makeRequest' "POST" u pa-makeRequest (APIRequestPostMultipart u param prt) =-    formDataBody body =<< makeRequest' "POST" u []+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) param+    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' :: MonadThrow m-             => HT.Method -- ^ HTTP request method (GET or POST)-             -> String -- ^ API Resource URL-             -> HT.SimpleQuery -- ^ Query-             -> m HTTP.Request+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.parseUrl url-    return $ req { HTTP.method = m-                 , HTTP.queryString = HT.renderSimpleQuery False query-                 , HTTP.checkStatus = \_ _ _ -> Nothing-                 }+    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} -{-# DEPRECATED api "use `getResponse =<< makeRequest'`" #-}-api :: TwitterBaseM m-    => HT.Method -- ^ HTTP request method (GET or POST)-    -> String -- ^ API Resource URL-    -> HT.SimpleQuery -- ^ Query-    -> TW m (Response (C.ResumableSource (TW m) ByteString))-api m url query =-    getResponse =<< makeRequest' m url query+class ResponseBodyType a where+    parseResponseBody ::+        Response (C.ConduitM () ByteString (ResourceT IO) ()) ->+        ResourceT IO (Response a) -getResponse :: TwitterBaseM m-            => HTTP.Request-            -> TW m (Response (C.ResumableSource (TW m) ByteString))-getResponse req = do-    proxy <- getProxy-    signedReq <- signOAuthTW $ req { HTTP.proxy = proxy }-    $(logDebug) $ T.pack $ "Signed Request: " ++ show signedReq-    mgr <- getManager+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-    $(logDebug) $ T.pack $ "Response Status: " ++ show (HTTP.responseStatus res)-    $(logDebug) $ T.pack $ "Response Header: " ++ show (HTTP.responseHeaders res)     return-        Response { responseStatus = HTTP.responseStatus res-                 , responseHeaders = HTTP.responseHeaders res-                 , responseBody = HTTP.responseBody res-                 }+        Response+            { responseStatus = HTTP.responseStatus res+            , responseHeaders = HTTP.responseHeaders res+            , responseBody = HTTP.responseBody res+            }  endpoint :: String endpoint = "https://api.twitter.com/1.1/" -getValue :: (MonadLogger m, MonadThrow m)-         => Response (C.ResumableSource m ByteString)-         -> m (Response Value)+getValue ::+    Response (C.ConduitM () ByteString (ResourceT IO) ()) ->+    ResourceT IO (Response Value) getValue res = do-    value <- responseBody res C.$$+- sinkJSON-    return $ res { responseBody = value }+    value <-+        C.runConduit $ responseBody res C..| sinkJSON+    return $ res {responseBody = value} -checkResponse :: Response Value-              -> Either TwitterError Value-checkResponse Response{..} =+checkResponse ::+    Response Value ->+    Either TwitterError Value+checkResponse Response {..} =     case responseBody ^? key "errors" of-        Just errs ->+        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@@ -128,121 +151,262 @@   where     sci = HT.statusCode responseStatus -getValueOrThrow :: (MonadThrow m, MonadLogger m, FromJSON a)-                => Response (C.ResumableSource m ByteString)-                -> m a+getValueOrThrow ::+    FromJSON a =>+    Response (C.ConduitM () ByteString (ResourceT IO) ()) ->+    ResourceT IO (Response a) getValueOrThrow res = do-    val <- getValueOrThrow' res-    case fromJSON val of-        Success r -> return r-        Error err -> monadThrow $ FromJSONError err--getValueOrThrow' :: (MonadLogger m, MonadThrow m)-                 => Response (C.ResumableSource m ByteString)-                 -> m Value-getValueOrThrow' res = do     res' <- getValue res     case checkResponse res' of-        Left err -> monadThrow err-        Right v -> return v+        Left err -> throwM err+        Right _ -> return ()+    case fromJSON (responseBody res') of+        Success r -> return $ res' {responseBody = r}+        Error err -> throwM $ FromJSONError err -call :: (TwitterBaseM m, FromJSON responseType)-     => APIRequest apiName responseType-     -> TW m responseType+-- | Perform an 'APIRequest' and then provide the response which is mapped to a suitable type of+-- <http://hackage.haskell.org/package/twitter-types twitter-types>.+--+-- Example:+--+-- @+-- user <- 'call' twInfo mgr $ 'accountVerifyCredentials'+-- print user+-- @+--+-- If you need raw JSON value which is parsed by <http://hackage.haskell.org/package/aeson aeson>,+-- use 'call'' to obtain it.+call ::+    ResponseBodyType responseType =>+    -- | Twitter Setting+    TWInfo ->+    HTTP.Manager ->+    APIRequest apiName responseType ->+    IO responseType call = call' -call' :: (TwitterBaseM m, FromJSON value)-      => APIRequest apiName responseType-      -> TW m value-call' req = do-    res <- getResponse =<< makeRequest req-    getValueOrThrow res+-- | 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 -sourceWithMaxId :: ( TwitterBaseM m-                   , FromJSON responseType-                   , AsStatus responseType-                   , HasMaxIdParam (APIRequest apiName [responseType])-                   )-                => APIRequest apiName [responseType]-                -> C.Source (TW m) responseType-sourceWithMaxId = loop+-- | 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 <- lift $ call req+        res <- liftIO $ call info mgr req         case getMinId res of             Just mid -> do                 CL.sourceList res-                loop $ req & maxId ?~ mid - 1+                loop $ req & #max_id ?~ mid - 1             Nothing -> CL.sourceList res     getMinId = minimumOf (traverse . status_id) -sourceWithMaxId' :: ( TwitterBaseM m-                    , HasMaxIdParam (APIRequest apiName [responseType])-                    )-                 => APIRequest apiName [responseType]-                 -> C.Source (TW m) Value-sourceWithMaxId' = loop+-- | 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 <- lift $ call' req-        case getMinId res of+        (res :: [Value]) <- liftIO $ call' info mgr req+        case minimumOf (traverse . key "id" . _Integer) res of             Just mid -> do                 CL.sourceList res-                loop $ req & maxId ?~ mid - 1+                loop $ req & #max_id ?~ mid - 1             Nothing -> CL.sourceList res-    getMinId = minimumOf (traverse . key "id" . _Integer) -sourceWithCursor :: ( TwitterBaseM m-                    , FromJSON responseType-                    , CursorKey ck-                    , HasCursorParam (APIRequest apiName (WithCursor ck responseType))-                    )-                 => APIRequest apiName (WithCursor ck responseType)-                 -> C.Source (TW m) responseType-sourceWithCursor req = loop (-1)+-- | 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 0 = CL.sourceNull-    loop cur = do-        res <- lift $ call $ req & cursor ?~ cur+    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 -sourceWithCursor' :: ( TwitterBaseM m-                     , FromJSON responseType-                     , CursorKey ck-                     , HasCursorParam (APIRequest apiName (WithCursor ck responseType))-                     )-                  => APIRequest apiName (WithCursor ck responseType)-                  -> C.Source (TW m) Value-sourceWithCursor' req = loop (-1)+-- | 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 :: FromJSON value-          => APIRequest apiName (WithCursor ck responseType)-          -> APIRequest apiName (WithCursor ck value)-    relax = unsafeCoerce-    loop 0 = CL.sourceNull-    loop cur = do-        res <- lift $ call $ relax $ req & cursor ?~ cur+    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 -sinkJSON :: ( MonadThrow m-            , MonadLogger m-            ) => C.Consumer ByteString m Value-sinkJSON = do-    js <- CA.sinkParser json-    $(logDebug) $ T.pack $ "Response JSON: " ++ show js-    return js+-- | 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 -sinkFromJSON :: ( FromJSON a-                , MonadThrow m-                , MonadLogger m-                ) => C.Consumer ByteString m a+-- | 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 -> monadThrow $ FromJSONError err+        Error err -> throwM $ FromJSONError err         Success r -> return r--showBS :: Show a => a -> ByteString-showBS = S8.pack . show
+ Web/Twitter/Conduit/Cursor.hs view
@@ -0,0 +1,74 @@+{-# 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)
+ Web/Twitter/Conduit/Lens.hs view
@@ -0,0 +1,55 @@+{-# 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)
− Web/Twitter/Conduit/Monad.hs
@@ -1,109 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE CPP #-}--module Web.Twitter.Conduit.Monad-       ( TW-       , TWToken (..)-       , TWInfo (..)-       , setCredential-       , runTW-       , runTWManager-       , getProxy-       , getManager-       , signOAuthTW-       , twitterOAuth-       )-       where--import Web.Authenticate.OAuth-import Network.HTTP.Conduit-import Data.Default-import Control.Monad.IO.Class-import Control.Monad.Trans.Reader-import Control.Monad.Trans.Resource---- $setup--- >>> :set -XOverloadedStrings--type TW m = ReaderT TWEnv m--data TWToken = TWToken-    { twOAuth :: OAuth-    , twCredential :: Credential-    } deriving Show-instance Default TWToken where-    def = TWToken twitterOAuth (Credential [])--data TWInfo = TWInfo-    { twToken :: TWToken-    , twProxy :: Maybe Proxy-    } deriving Show-instance Default TWInfo where-    def = TWInfo-        { twToken = def-        , twProxy = Nothing-        }--twitterOAuth :: OAuth-twitterOAuth =-    def { oauthServerName = "twitter"-        , oauthRequestUri = "https://api.twitter.com/oauth/request_token"-        , oauthAccessTokenUri = "https://api.twitter.com/oauth/access_token"-        , oauthAuthorizeUri = "https://api.twitter.com/oauth/authorize"-        , oauthConsumerKey = error "You MUST specify oauthConsumerKey parameter."-        , oauthConsumerSecret = error "You MUST specify oauthConsumerSecret parameter."-        , oauthSignatureMethod = HMACSHA1-        , oauthCallback = Nothing-        }--data TWEnv = TWEnv-    { twInfo :: TWInfo-    , twManager :: Manager-    }---- | set OAuth keys and Credentials to TWInfo.------ >>> let proxy = Proxy "localhost" 8080--- >>> let twinfo = def { twProxy = Just proxy }--- >>> let oauth = twitterOAuth { oauthConsumerKey = "consumer_key", oauthConsumerSecret = "consumer_secret" }--- >>> let credential = Credential [("oauth_token","...")]--- >>> let twinfo2 = setCredential oauth credential twinfo--- >>> oauthConsumerKey . twOAuth . twToken $ twinfo2--- "consumer_key"--- >>> twProxy twinfo2 == Just proxy--- True-setCredential :: OAuth -> Credential -> TWInfo -> TWInfo-setCredential oa cred env-  = TWInfo-    { twToken = TWToken oa cred-    , twProxy = twProxy env-    }---- | create a new http-conduit manager and run TW monad.------ >>> runTW def getProxy--- Nothing--- >>> runTW def $ asks (twCredential . twToken . twInfo)--- Credential {unCredential = []}-runTW :: ( MonadBaseControl IO m-         , MonadIO m-         ) => TWInfo -> TW (ResourceT m) a -> m a-runTW info st = withManager $ \mgr -> runTWManager info mgr st--runTWManager :: MonadBaseControl IO m => TWInfo -> Manager -> TW (ResourceT m) a -> ResourceT m a-runTWManager info mgr st = runReaderT st $ TWEnv info mgr--getProxy ::Monad m => TW m (Maybe Proxy)-getProxy = asks (twProxy . twInfo)--getManager :: Monad m => TW m Manager-getManager = asks twManager--signOAuthTW :: MonadIO m-            => Request-            -> TW m Request-signOAuthTW req = do-    TWToken oa cred <- asks (twToken . twInfo)-    signOAuth oa cred req
Web/Twitter/Conduit/Parameters.hs view
@@ -1,115 +1,78 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TemplateHaskell #-} -module Web.Twitter.Conduit.Parameters-       ( Parameters (..)-       , HasSinceIdParam (..)-       , HasCountParam (..)-       , HasMaxIdParam (..)-       , HasPageParam (..)-       , HasCursorParam (..)-       , HasTrimUserParam (..)-       , HasExcludeRepliesParam (..)-       , HasContributorDetailsParam (..)-       , HasIncludeEntitiesParam (..)-       , HasIncludeUserEntitiesParam (..)-       , HasIncludeRtsParam (..)-       , HasIncludeMyRetweetParam (..)-       , HasInReplyToStatusIdParam (..)-       , HasDisplayCoordinatesParam (..)-       , HasPossiblySensitiveParam (..)-       , HasLangParam (..)-       , HasLocaleParam (..)-       , HasUntilParam (..)-       , HasSkipStatusParam (..)-       , HasFollowParam (..)-       , HasMapParam (..)-       , HasMediaIdsParam (..)--       , UserParam(..)-       , UserListParam(..)-       , ListParam(..)-       , mkUserParam-       , mkUserListParam-       , mkListParam-       ) where+module Web.Twitter.Conduit.Parameters (+    UserParam (..),+    UserListParam (..),+    ListParam (..),+    MediaData (..),+    TweetMode (..),+    mkUserParam,+    mkUserListParam,+    mkListParam,+) where -import Prelude as P-import Web.Twitter.Conduit.Parameters.Internal-import Web.Twitter.Conduit.Parameters.TH+import qualified Data.Text as T+import Network.HTTP.Client (RequestBody)+import Web.Twitter.Conduit.Request.Internal (APIQuery, PV (..), ParameterValue (..)) import Web.Twitter.Types-import Data.Time.Calendar (Day)-import Data.Text.Strict.Lens-import Data.Text (Text)-import Control.Lens -import qualified Network.HTTP.Types as HT-import qualified Data.ByteString.Char8 as S8+-- $setup+-- >>> import Web.Twitter.Conduit.Request.Internal +-- Required parameters+ data UserParam = UserIdParam UserId | ScreenNameParam String-               deriving (Show, Eq)+    deriving (Show, Eq) data UserListParam = UserIdListParam [UserId] | ScreenNameListParam [String]-                   deriving (Show, Eq)+    deriving (Show, Eq) data ListParam = ListIdParam Integer | ListNameParam String-               deriving (Show, Eq)--defineHasParamClass "count" ''Integer 'readShow-defineHasParamClass "since_id" ''Integer 'readShow-defineHasParamClass "max_id" ''Integer 'readShow-defineHasParamClass "page" ''Integer 'readShow-defineHasParamClass "cursor" ''Integer 'readShow-defineHasParamClass "trim_user" ''Bool 'booleanQuery-defineHasParamClass "exclude_replies" ''Bool 'booleanQuery-defineHasParamClass "contributor_details" ''Bool 'booleanQuery-defineHasParamClass "include_entities" ''Bool 'booleanQuery-defineHasParamClass "include_user_entities" ''Bool 'booleanQuery-defineHasParamClass "include_rts" ''Bool 'booleanQuery-defineHasParamClass "include_my_retweet" ''Bool 'booleanQuery-defineHasParamClass "in_reply_to_status_id" ''StatusId 'readShow-defineHasParamClass "display_coordinates" ''Bool 'booleanQuery-defineHasParamClass "possibly_sensitive" ''Bool 'booleanQuery-defineHasParamClass "lang" ''Text 'utf8-defineHasParamClass "locale" ''Text 'utf8-defineHasParamClass "until" ''Day 'readShow-defineHasParamClass "skip_status" ''Bool 'booleanQuery-defineHasParamClass "follow" ''Bool 'booleanQuery-defineHasParamClass "map" ''Bool 'booleanQuery-defineHasParamClass' "media_ids" [t|[Integer]|] 'integerArrayQuery+    deriving (Show, Eq)+data MediaData+    = MediaFromFile FilePath+    | MediaRequestBody FilePath RequestBody  -- | converts 'UserParam' to 'HT.SimpleQuery'. ----- >>> mkUserParam $ UserIdParam 123456+-- >>> makeSimpleQuery . mkUserParam $ UserIdParam 123456 -- [("user_id","123456")]--- >>> mkUserParam $ ScreenNameParam "thimura"+-- >>> makeSimpleQuery . mkUserParam $ ScreenNameParam "thimura" -- [("screen_name","thimura")]-mkUserParam :: UserParam -> HT.SimpleQuery-mkUserParam (UserIdParam uid) =  [("user_id", readShow # uid)]-mkUserParam (ScreenNameParam sn) = [("screen_name", S8.pack sn)]+mkUserParam :: UserParam -> APIQuery+mkUserParam (UserIdParam uid) = [("user_id", PVInteger uid)]+mkUserParam (ScreenNameParam sn) = [("screen_name", PVString . T.pack $ sn)]  -- | converts 'UserListParam' to 'HT.SimpleQuery'. ----- >>> mkUserListParam $ UserIdListParam [123456]+-- >>> makeSimpleQuery . mkUserListParam $ UserIdListParam [123456] -- [("user_id","123456")]--- >>> mkUserListParam $ UserIdListParam [123456, 654321]+-- >>> makeSimpleQuery . mkUserListParam $ UserIdListParam [123456, 654321] -- [("user_id","123456,654321")]--- >>> mkUserListParam $ ScreenNameListParam ["thimura", "NikaidouShinku"]+-- >>> makeSimpleQuery . mkUserListParam $ ScreenNameListParam ["thimura", "NikaidouShinku"] -- [("screen_name","thimura,NikaidouShinku")]-mkUserListParam :: UserListParam -> HT.SimpleQuery-mkUserListParam (UserIdListParam uids) =  [("user_id", uids ^.. traversed . re readShow & S8.intercalate ",")]-mkUserListParam (ScreenNameListParam sns) = [("screen_name", S8.intercalate "," . P.map S8.pack $ sns)]+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'. ----- >>> mkListParam $ ListIdParam 123123+-- >>> makeSimpleQuery . mkListParam $ ListIdParam 123123 -- [("list_id","123123")]--- >>> mkListParam $ ListNameParam "thimura/haskell"+-- >>> makeSimpleQuery . mkListParam $ ListNameParam "thimura/haskell" -- [("slug","haskell"),("owner_screen_name","thimura")]-mkListParam :: ListParam -> HT.SimpleQuery-mkListParam (ListIdParam lid) =  [("list_id", readShow # lid)]+mkListParam :: ListParam -> APIQuery+mkListParam (ListIdParam lid) = [("list_id", PVInteger lid)] mkListParam (ListNameParam listname) =-    [("slug", S8.pack lstName),-     ("owner_screen_name", S8.pack screenName)]+    [ ("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
− Web/Twitter/Conduit/Parameters/Internal.hs
@@ -1,94 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE Rank2Types #-}--module Web.Twitter.Conduit.Parameters.Internal-       ( Parameters(..)-       , readShow-       , booleanQuery-       , integerArrayQuery-       , wrappedParam-       ) where--import qualified Network.HTTP.Types as HT-import qualified Data.ByteString as S-import qualified Data.ByteString.Char8 as S8-import Data.Maybe-import Control.Lens--class Parameters a where-    params :: Lens' a HT.SimpleQuery---- | This 'Prism' convert from a 'ByteString' to some value based on 'Read' and 'Show'------ >>> readShow # 2--- "2"--- >>> "1024" ^? readShow :: Maybe Integer--- Just 1024-readShow :: (Read a, Show a) => Prism' S.ByteString a-readShow = prism' (S8.pack . show) (readMaybe . S8.unpack)-  where-    readMaybe str = case [x | (x, t) <- reads str, ("", "") <- lex t] of-        [x] -> Just x-        _ -> Nothing---- | This 'Prism' convert from a 'ByteString' to 'Bool' value.------ >>> booleanQuery # True--- "true"--- >>> booleanQuery # False--- "false"--- >>> "true" ^? booleanQuery--- Just True--- >>> "1" ^? booleanQuery--- Just True--- >>> "t" ^? booleanQuery--- Just True--- >>> "test" ^? booleanQuery--- Just False-booleanQuery :: Prism' S.ByteString Bool-booleanQuery = prism' bs sb-  where-    bs True = "true"-    bs False = "false"-    sb "true" = Just True-    sb "1" = Just True-    sb "t" = Just True-    sb _ = Just False---- | This 'Prism' convert from a 'ByteString' to the array of 'Integer' value.------ This is not a valid Prism, for example:------ @--- "1, 2" ^? integerArrayQuery == Just [1,2]--- integerArrayQuery # [1,2] != "1, 2"--- @------ >>> integerArrayQuery # [1]--- "1"--- >>> integerArrayQuery # [1,2234,3]--- "1,2234,3"--- >>> "1,2234,3" ^? integerArrayQuery--- Just [1,2234,3]--- >>> "" ^? integerArrayQuery--- Just []--- >>> "hoge,2" ^? integerArrayQuery--- Nothing-integerArrayQuery :: Prism' S.ByteString [Integer]-integerArrayQuery = prism' bs_arr arr_bs-  where-    bs_arr xs = S8.intercalate "," $ xs ^.. traversed . re readShow-    arr_bs str = chkValid $ map (^? readShow) $ S8.split ',' str-    chkValid arr =-        if all isJust arr-        then Just (catMaybes arr)-        else Nothing--wrappedParam :: Parameters p => S.ByteString -> Prism' S.ByteString a -> Lens' p (Maybe a)-wrappedParam key aSBS = lens getter setter-   where-     getter = preview $ params . to (lookup key) . _Just . aSBS-     setter = flip (over params . replace key)-     replace k (Just v) = ((k, aSBS # v):) . dropAssoc k-     replace k Nothing = dropAssoc k-     dropAssoc k = filter ((/= k) . fst)
− Web/Twitter/Conduit/Parameters/TH.hs
@@ -1,63 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE Rank2Types #-}-{-# LANGUAGE TemplateHaskell #-}--module Web.Twitter.Conduit.Parameters.TH where--import Web.Twitter.Conduit.Parameters.Internal-import Language.Haskell.TH-import Control.Lens-import Data.Char--snakeToLowerCamel :: String -> String-snakeToLowerCamel [] = []-snakeToLowerCamel "_" = []-snakeToLowerCamel ('_':x:xs) = toUpper x : snakeToLowerCamel xs-snakeToLowerCamel str = f ++ snakeToLowerCamel next-  where (f, next) = span (/= '_') str--snakeToUpperCamel :: String -> String-snakeToUpperCamel = upcase . snakeToLowerCamel-  where-    upcase [] = []-    upcase (x:xs) = toUpper x : xs--paramNameToClassName :: String -> String-paramNameToClassName paramName = "Has" ++ snakeToUpperCamel paramName ++ "Param"--defineHasParamClass :: String -- ^ parameter name-                    -> Name -- ^ parameter type-                    -> Name -- ^ a Prism-                    -> Q [Dec]-defineHasParamClass paramName typeN =-    defineHasParamClass' paramName (conT typeN)--defineHasParamClass' :: String -> TypeQ -> Name -> Q [Dec]-defineHasParamClass' paramName typeQ =-    defineHasParamClass'' cNameS fNameS paramName typeQ-  where-    cNameS = paramNameToClassName paramName-    fNameS = snakeToLowerCamel paramName--defineHasParamClass'' :: String -> String -> String -> TypeQ -> Name -> Q [Dec]-defineHasParamClass'' cNameS fNameS paramName typeQ prismN = do-    a <- newName "a"-    cName <- newName cNameS-    fName <- newName fNameS-    let cCxt = cxt [classP ''Parameters [varT a]]-        tySig = sigD fName (appT (appT (conT ''Lens') (varT a)) (appT (conT ''Maybe) typeQ))-        valDef = valD (varP fName) (normalB (appE (appE (varE 'wrappedParam) (litE (stringL paramName))) (varE prismN))) []-    dec <- classD cCxt cName [PlainTV a] [] [tySig, valDef]-    return [dec]--deriveHasParamInstances :: Name -- ^ target data type name-                        -> [String] -- ^ parameter name-                        -> Q [Dec]-deriveHasParamInstances typName paramNameList =-    mapM mkInstance cNameStrList-  where-    cNameStrList = map paramNameToClassName paramNameList-    mkInstance cn = instanceD (return []) (appT (conT (mkName cn)) targetType) []-    targetType = do-        a <- newName "a"-        appT (appT (conT (mkName "APIRequest")) (conT typName)) (varT a)
Web/Twitter/Conduit/Request.hs view
@@ -1,45 +1,39 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE Rank2Types #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE CPP #-}--module Web.Twitter.Conduit.Request-       ( APIRequest(..)-       ) where+{-# LANGUAGE TypeFamilies #-} -import Web.Twitter.Conduit.Parameters+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 Control.Applicative+import Web.Twitter.Conduit.Request.Internal --- In GHC 7.4.2, the following test fails with Overlapping instances error.--- It may be caused by #5820 "defining instance in GHCi leads to duplicated instances".--- So, we bypass below tests when GHC version older than 7.6.--- see details: https://ghc.haskell.org/trac/ghc/ticket/5820-#if __GLASGOW_HASKELL__ >= 706 -- $setup--- >>> :set -XOverloadedStrings -XRank2Types -XEmptyDataDecls -XFlexibleInstances -XOverlappingInstances -XIncoherentInstances+-- >>> :set -XOverloadedStrings -XDataKinds -XTypeOperators -- >>> import Control.Lens--- >>> import Data.Default--- >>> data SampleApi+-- >>> import Web.Twitter.Conduit.Parameters -- >>> type SampleId = Integer--- >>> instance HasCountParam (APIRequest SampleApi [SampleId])--- >>> instance HasMaxIdParam (APIRequest SampleApi [SampleId])--- >>> let sampleApiRequest :: APIRequest SampleApi [SampleId]; sampleApiRequest = APIRequestGet "https://api.twitter.com/sample/api.json" def+-- >>> 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@.--- In addition, @'APIRequest' SampleApi [SampleId]@ is a instance of both of 'HasCountParam' and 'HasMaxIdParam'. -- -- @--- data 'SampleApi'--- type 'SampleId' = 'Integer'--- instance 'HasCountParam' ('APIRequest' 'SampleApi' ['SampleId'])--- instance 'HasMaxIdParam' ('APIRequest' 'SampleApi' ['SampleId'])--- 'sampleApiRequest' :: 'APIRequest' 'SampleApi' ['SampleId']--- 'sampleApiRequest' = 'APIRequestGet' \"https:\/\/api.twitter.com\/sample\/api.json\" 'def'+-- 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]@ :@@ -47,33 +41,41 @@ -- >>> sampleApiRequest ^. params -- [] ----- And update request parameters.+-- 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 & maxId ?~ 1234567890) ^. params--- [("max_id","1234567890"),("count","100")]--- >>> (sampleApiRequest & count ?~ 100 & maxId ?~ 1234567890 & count .~ Nothing) ^. params--- [("max_id","1234567890")]-#endif-data APIRequest apiName responseType-    = APIRequestGet-      { _url :: String-      , _params :: HT.SimpleQuery-      }-    | APIRequestPost-      { _url :: String-      , _params :: HT.SimpleQuery-      }-    | APIRequestPostMultipart-      { _url :: String-      , _params :: HT.SimpleQuery-      , _part :: [Part]-      }-instance Parameters (APIRequest apiName responseType) where-    params f (APIRequestGet u pa) = APIRequestGet u <$> f pa-    params f (APIRequestPost u pa) = APIRequestPost u <$> f pa-    params f (APIRequestPostMultipart u pa prt) =-        (\p -> APIRequestPostMultipart u p prt) <$> f pa+-- >>> (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 (APIRequestGet u p) = "APIRequestGet " ++ show u ++ " " ++ show p-    show (APIRequestPost u p) = "APIRequestPost " ++ show u ++ " " ++ show p-    show (APIRequestPostMultipart u p _) = "APIRequestPostMultipart " ++ show u ++ " " ++ show p+    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)
+ Web/Twitter/Conduit/Request/Internal.hs view
@@ -0,0 +1,110 @@+{-# 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))
+ Web/Twitter/Conduit/Response.hs view
@@ -0,0 +1,59 @@+{-# 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: <https://dev.twitter.com/docs/error-codes-responses>+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
Web/Twitter/Conduit/Status.hs view
@@ -1,315 +1,75 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE EmptyDataDecls #-}-#if __GLASGOW_HASKELL__ >= 704-{-# LANGUAGE ConstraintKinds #-}-#endif+module Web.Twitter.Conduit.Status (+    -- * Notice+    -- $notice -module Web.Twitter.Conduit.Status-       (-       -- * Timelines-         StatusesMentionsTimeline-       , mentionsTimeline-       , StatusesUserTimeline-       , userTimeline-       , StatusesHomeTimeline-       , homeTimeline-       , StatusesRetweetsOfMe-       , retweetsOfMe-       -- * Tweets-       , StatusesRetweetsId-       , retweetsId-       , StatusesShowId-       , showId-       , StatusesDestroyId-       , destroyId-       , StatusesUpdate-       , update-       , StatusesRetweetId-       , retweetId-       , MediaData (..)-       , StatusesUpdateWithMedia-       , updateWithMedia-       -- , oembed-       -- , retweetersIds-       , StatusesLookup-       , lookup-       ) where+    -- * Timelines+    mentionsTimeline,+    userTimeline,+    homeTimeline,+    retweetsOfMe, -import Prelude hiding ( lookup )-import Web.Twitter.Conduit.Base-import Web.Twitter.Conduit.Request-import Web.Twitter.Conduit.Types+    -- * 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.Parameters.TH+import Web.Twitter.Conduit.Request import Web.Twitter.Types -import qualified Data.ByteString as S-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import Network.HTTP.Client.MultipartFormData-import Data.Default---- $setup--- >>> :set -XOverloadedStrings--- >>> import Control.Lens---- * Timelines+import Prelude hiding (lookup) -data StatusesMentionsTimeline--- | Returns query data asks the most recent mentions for the authenticating user.------ You can perform a query using 'call':------ @--- res <- 'call' 'mentionsTimeline'--- @+-- $notice ----- >>> mentionsTimeline--- APIRequestGet "https://api.twitter.com/1.1/statuses/mentions_timeline.json" []+-- This module provides aliases of statuses API, for backward compatibility.+ mentionsTimeline :: APIRequest StatusesMentionsTimeline [Status]-mentionsTimeline = APIRequestGet (endpoint ++ "statuses/mentions_timeline.json") def-deriveHasParamInstances ''StatusesMentionsTimeline-    [ "count"-    , "since_id"-    , "max_id"-    , "trim_user"-    , "contributor_details"-    , "include_entities"-    ]+mentionsTimeline = statusesMentionsTimeline+{-# DEPRECATED mentionsTimeline "Please use Web.Twitter.Conduit.API.statusesMentionsTimeline" #-} -data StatusesUserTimeline--- | Returns query data asks a collection of the most recent Tweets posted by the user indicated by the screen_name or user_id parameters.------ You can perform a search query using 'call':------ @--- res <- 'call' $ 'userTimeline' ('ScreenNameParam' \"thimura\")--- @------ >>> userTimeline (ScreenNameParam "thimura")--- APIRequestGet "https://api.twitter.com/1.1/statuses/user_timeline.json" [("screen_name","thimura")]--- >>> userTimeline (ScreenNameParam "thimura") & includeRts ?~ True & count ?~ 200--- APIRequestGet "https://api.twitter.com/1.1/statuses/user_timeline.json" [("count","200"),("include_rts","true"),("screen_name","thimura")] userTimeline :: UserParam -> APIRequest StatusesUserTimeline [Status]-userTimeline q = APIRequestGet (endpoint ++ "statuses/user_timeline.json") (mkUserParam q)-deriveHasParamInstances ''StatusesUserTimeline-    [ "count"-    , "since_id"-    , "max_id"-    , "trim_user"-    , "exclude_replies"-    , "contributor_details"-    , "include_rts"-    ]+userTimeline = statusesUserTimeline+{-# DEPRECATED userTimeline "Please use Web.Twitter.Conduit.API.statusesUserTimeline" #-} -data StatusesHomeTimeline--- | Returns query data asks a collection of the most recentTweets and retweets posted by the authenticating user and the users they follow.------ You can perform a search query using 'call':------ @--- res <- 'call' 'homeTimeline'--- @------ >>> homeTimeline--- APIRequestGet "https://api.twitter.com/1.1/statuses/home_timeline.json" []--- >>> homeTimeline & count ?~ 200--- APIRequestGet "https://api.twitter.com/1.1/statuses/home_timeline.json" [("count","200")] homeTimeline :: APIRequest StatusesHomeTimeline [Status]-homeTimeline = APIRequestGet (endpoint ++ "statuses/home_timeline.json") def-deriveHasParamInstances ''StatusesHomeTimeline-    [ "count"-    , "since_id"-    , "max_id"-    , "trim_user"-    , "exclude_replies"-    , "contributor_details"-    , "include_entities"-    ]+homeTimeline = statusesHomeTimeline+{-# DEPRECATED homeTimeline "Please use Web.Twitter.Conduit.API.statusesHomeTimeline" #-} -data StatusesRetweetsOfMe--- | Returns query data asks the most recent tweets authored by the authenticating user that have been retweeted by others.------ You can perform a search query using 'call':------ @--- res <- 'call' 'retweetsOfMe'--- @------ >>> retweetsOfMe--- APIRequestGet "https://api.twitter.com/1.1/statuses/retweets_of_me.json" []--- >>> retweetsOfMe & count ?~ 100--- APIRequestGet "https://api.twitter.com/1.1/statuses/retweets_of_me.json" [("count","100")] retweetsOfMe :: APIRequest StatusesRetweetsOfMe [Status]-retweetsOfMe = APIRequestGet (endpoint ++ "statuses/retweets_of_me.json") def-deriveHasParamInstances ''StatusesRetweetsOfMe-    [ "count"-    , "since_id"-    , "max_id"-    , "trim_user"-    , "include_entities"-    , "include_user_entities"-    ]---- * Tweets+retweetsOfMe = statusesRetweetsOfMe+{-# DEPRECATED retweetsOfMe "Please use Web.Twitter.Conduit.API.statusesRetweetsOfMe" #-} -data StatusesRetweetsId--- | Returns query data asks the most recent tweets authored by the authenticating user that have been retweeted by others.------ You can perform a search query using 'call':------ @--- res <- 'call' '$' 'retweetsId' 1234567890--- @------ >>> retweetsId 1234567890--- APIRequestGet "https://api.twitter.com/1.1/statuses/retweets/1234567890.json" []--- >>> retweetsId 1234567890 & count ?~ 100--- APIRequestGet "https://api.twitter.com/1.1/statuses/retweets/1234567890.json" [("count","100")] retweetsId :: StatusId -> APIRequest StatusesRetweetsId [RetweetedStatus]-retweetsId status_id = APIRequestGet uri def-  where uri = endpoint ++ "statuses/retweets/" ++ show status_id ++ ".json"-deriveHasParamInstances ''StatusesRetweetsId-    [ "count"-    , "trim_user"-    ]+retweetsId = statusesRetweetsId+{-# DEPRECATED retweetsId "Please use Web.Twitter.Conduit.API.statusesRetweetsId" #-} -data StatusesShowId--- | Returns query data asks a single Tweet, specified by the id parameter.------ You can perform a search query using 'call':------ @--- res <- 'call' '$' 'showId' 1234567890--- @------ >>> showId 1234567890--- APIRequestGet "https://api.twitter.com/1.1/statuses/show/1234567890.json" []--- >>> showId 1234567890 & includeMyRetweet ?~ True--- APIRequestGet "https://api.twitter.com/1.1/statuses/show/1234567890.json" [("include_my_retweet","true")] showId :: StatusId -> APIRequest StatusesShowId Status-showId status_id = APIRequestGet uri def-  where uri = endpoint ++ "statuses/show/" ++ show status_id ++ ".json"-deriveHasParamInstances ''StatusesShowId-    [ "trim_user"-    , "include_my_retweet"-    , "include_entities"-    ]+showId = statusesShowId+{-# DEPRECATED showId "Please use Web.Twitter.Conduit.API.statusesShowId" #-} -data StatusesDestroyId--- | Returns post data which destroys the status specified by the require ID parameter.------ You can perform a search query using 'call':------ @--- res <- 'call' '$' 'destroyId' 1234567890--- @------ >>> destroyId 1234567890--- APIRequestPost "https://api.twitter.com/1.1/statuses/destroy/1234567890.json" [] destroyId :: StatusId -> APIRequest StatusesDestroyId Status-destroyId status_id = APIRequestPost uri def-  where uri = endpoint ++ "statuses/destroy/" ++ show status_id ++ ".json"-deriveHasParamInstances ''StatusesDestroyId-    [ "trim_user"-    ]+destroyId = statusesDestroyId+{-# DEPRECATED destroyId "Please use Web.Twitter.Conduit.API.statusesDestroyId" #-} -data StatusesUpdate--- | Returns post data which updates the authenticating user's current status.--- To upload an image to accompany the tweet, use 'updateWithMedia'.------ You can perform a search query using 'call':------ @--- res <- 'call' '$' 'update' \"Hello World\"--- @------ >>> update "Hello World"--- APIRequestPost "https://api.twitter.com/1.1/statuses/update.json" [("status","Hello World")]--- >>> update "Hello World" & inReplyToStatusId ?~ 1234567890--- APIRequestPost "https://api.twitter.com/1.1/statuses/update.json" [("in_reply_to_status_id","1234567890"),("status","Hello World")]-update :: T.Text -> APIRequest StatusesUpdate Status-update status = APIRequestPost uri [("status", T.encodeUtf8 status)]-  where uri = endpoint ++ "statuses/update.json"-deriveHasParamInstances ''StatusesUpdate-    [ "in_reply_to_status_id"-    -- , "lat_long"-    -- , "place_id"-    , "display_coordinates"-    , "trim_user"-    , "media_ids"-    ]+update :: Text -> APIRequest StatusesUpdate Status+update = statusesUpdate+{-# DEPRECATED update "Please use Web.Twitter.Conduit.API.statusesUpdate" #-} -data StatusesRetweetId--- | Returns post data which retweets a tweet, specified by ID.------ You can perform a search query using 'call':------ @--- res <- 'call' '$' 'retweetId' 1234567890--- @------ >>> retweetId 1234567890--- APIRequestPost "https://api.twitter.com/1.1/statuses/retweet/1234567890.json" [] retweetId :: StatusId -> APIRequest StatusesRetweetId RetweetedStatus-retweetId status_id = APIRequestPost uri def-  where uri = endpoint ++ "statuses/retweet/" ++ show status_id ++ ".json"-deriveHasParamInstances ''StatusesRetweetId-    [ "trim_user"-    ]+retweetId = statusesRetweetId+{-# DEPRECATED retweetId "Please use Web.Twitter.Conduit.API.statusesRetweetId" #-} -data StatusesUpdateWithMedia--- | Returns post data which updates the authenticating user's current status and attaches media for upload.------ You can perform a search query using 'call':------ @--- res <- 'call' '$' 'updateWithMedia' \"Hello World\" ('MediaFromFile' \"/home/thimura/test.jpeg\")--- @------ >>> updateWithMedia "Hello World" (MediaFromFile "/home/fuga/test.jpeg")--- APIRequestPostMultipart "https://api.twitter.com/1.1/statuses/update_with_media.json" [("status","Hello World")]-updateWithMedia :: T.Text-                -> MediaData-                -> APIRequest StatusesUpdateWithMedia Status-updateWithMedia tweet mediaData =-    APIRequestPostMultipart uri [("status", T.encodeUtf8 tweet)] [mediaBody mediaData]-  where-    uri = endpoint ++ "statuses/update_with_media.json"-    mediaBody (MediaFromFile fp) = partFileSource "media[]" fp-    mediaBody (MediaRequestBody filename filebody) = partFileRequestBody "media[]" filename filebody-deriveHasParamInstances ''StatusesUpdateWithMedia-    [ "possibly_sensitive"-    , "in_reply_to_status_id"-    -- , "lat_long"-    -- , "place_id"-    , "display_coordinates"-    ]+updateWithMedia :: Text -> MediaData -> APIRequest StatusesUpdateWithMedia Status+updateWithMedia = statusesUpdateWithMedia+{-# DEPRECATED updateWithMedia "Please use Web.Twitter.Conduit.API.statusesUpdateWithMedia" #-} -data StatusesLookup--- | 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' '$' 'lookup' [20, 432656548536401920]--- @------ >>> lookup [10]--- APIRequestGet "https://api.twitter.com/1.1/statuses/lookup.json" [("id","10")]--- >>> lookup [10, 432656548536401920]--- APIRequestGet "https://api.twitter.com/1.1/statuses/lookup.json" [("id","10,432656548536401920")]--- >>> lookup [10, 432656548536401920] & includeEntities ?~ True--- APIRequestGet "https://api.twitter.com/1.1/statuses/lookup.json" [("include_entities","true"),("id","10,432656548536401920")]-lookup :: [ StatusId ] -> APIRequest StatusesLookup [Status]-lookup ids = APIRequestGet (endpoint ++ "statuses/lookup.json") [("id", S.intercalate "," . Prelude.map showBS $ ids)]-deriveHasParamInstances ''StatusesLookup-    [ "include_entities"-    , "trim_user"-    , "map"-    ]+lookup :: [StatusId] -> APIRequest StatusesLookup [Status]+lookup = statusesLookup+{-# DEPRECATED lookup "Please use Web.Twitter.Conduit.API.statusesLookup" #-}
Web/Twitter/Conduit/Stream.hs view
@@ -1,80 +1,123 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE EmptyDataDecls #-}-{-# LANGUAGE CPP #-}-#if __GLASGOW_HASKELL__ >= 704 {-# LANGUAGE ConstraintKinds #-}-#endif+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-} -module Web.Twitter.Conduit.Stream-       (-       -- * StreamingAPI-         Userstream-       , userstream-       , StatusesFilter-       , statusesFilterByFollow-       , statusesFilterByTrack-       -- , statusesFilterByLocation-       -- , statusesSample-       -- , statusesFirehose-       -- , sitestream-       -- , sitestream'-       , stream-       , stream'-  ) where+module Web.Twitter.Conduit.Stream (+    -- * StreamingAPI+    Userstream,+    userstream,+    StatusesFilter,+    FilterParameter (..),+    statusesFilter,+    statusesFilterByFollow,+    statusesFilterByTrack,+    -- , statusesFilterByLocation+    -- , statusesSample+    -- , statusesFirehose+    -- , sitestream+    -- , sitestream'+    stream,+    stream',+) where -import Web.Twitter.Conduit.Types import Web.Twitter.Conduit.Base-import Web.Twitter.Conduit.Monad-import Web.Twitter.Types 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.Conduit.Internal as CI+import qualified Data.List as L import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Data.ByteString as S-import Control.Monad.IO.Class-import Data.Aeson--#if MIN_VERSION_conduit(1,0,16)-($=+) :: MonadIO m-      => CI.ResumableSource m a-      -> CI.Conduit a m o-      -> m (CI.ResumableSource m o)-($=+) = (return .) . (C.$=+)-#else-rsrc $=+ cndt = do-    (src, finalizer) <- C.unwrapResumable rsrc-    return $ CI.ResumableSource (src C.$= cndt) finalizer-#endif+import qualified Network.HTTP.Conduit as HTTP -stream :: (TwitterBaseM m, FromJSON responseType)-       => APIRequest apiName responseType-       -> TW m (C.ResumableSource (TW m) responseType)+stream ::+    ( MonadResource m+    , FromJSON responseType+    , MonadThrow m+    ) =>+    TWInfo ->+    HTTP.Manager ->+    APIRequest apiName responseType ->+    m (C.ConduitM () responseType m ()) stream = stream' -stream' :: (TwitterBaseM m, FromJSON value)-        => APIRequest apiName responseType-        -> TW m (C.ResumableSource (TW m) value)-stream' req = do-    rsrc <- getResponse =<< makeRequest req-    responseBody rsrc $=+ CL.sequence sinkFromJSON+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 -data Userstream userstream :: APIRequest Userstream StreamingAPI-userstream = APIRequestGet "https://userstream.twitter.com/1.1/user.json" []+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" -data StatusesFilter+-- | 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 =-    APIRequestPost statusesFilterEndpoint [("follow", S.intercalate "," . map showBS $ userIds)]+statusesFilterByFollow userIds = statusesFilter [Follow userIds] -statusesFilterByTrack :: T.Text -- ^ keyword-                      -> APIRequest StatusesFilter StreamingAPI-statusesFilterByTrack keyword =-    APIRequestPost statusesFilterEndpoint [("track", T.encodeUtf8 keyword)]+-- | 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+     ]
Web/Twitter/Conduit/Types.hs view
@@ -1,154 +1,64 @@-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE EmptyDataDecls #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE CPP #-} -module Web.Twitter.Conduit.Types-       ( Response (..)-       , TwitterError (..)-       , TwitterErrorMessage (..)-       , CursorKey (..)-       , IdsCursorKey-       , UsersCursorKey-       , WithCursor (..)-       , MediaData (..)-#if !MIN_VERSION_twitter_types(0,5,0)-       , UploadedMedia (..)-       , ImageSizeType (..)-#endif-       ) where---import Control.Applicative-import Control.Exception-import Data.Aeson-import Data.Data-import Data.Foldable (Foldable)-import Data.Monoid-import qualified Data.Text as T-import Data.Traversable (Traversable)-import Network.HTTP.Client (RequestBody)-import Network.HTTP.Types (Status, ResponseHeaders)-import Web.Twitter.Types (checkError)---- $setup--- >>> type UserId = Integer---class CursorKey a where-    cursorKey :: a -> T.Text---- | Phantom type to specify the key which point out the content in the response.-data IdsCursorKey-instance CursorKey IdsCursorKey where-    cursorKey = const "ids"---- | Phantom type to specify the key which point out the content in the response.-data UsersCursorKey-instance CursorKey UsersCursorKey where-    cursorKey = const "users"--#if __GLASGOW_HASKELL__ >= 706--- | A wrapper for API responses which have "next_cursor" field.------ The first type parameter of 'WithCursor' specifies the field name of contents.------ >>> let Just res = decode "{\"previous_cursor\": 0, \"next_cursor\": 1234567890, \"ids\": [1111111111]}" :: Maybe (WithCursor IdsCursorKey UserId)--- >>> nextCursor res--- 1234567890--- >>> contents res--- [1111111111]------ >>> let Just res = decode "{\"previous_cursor\": 0, \"next_cursor\": 0, \"users\": [1000]}" :: Maybe (WithCursor UsersCursorKey UserId)--- >>> nextCursor res--- 0--- >>> contents res--- [1000]-#endif-data WithCursor cursorKey wrapped = WithCursor-    { previousCursor :: Integer-    , nextCursor :: Integer-    , contents :: [wrapped]-    } deriving Show--instance (FromJSON wrapped, CursorKey c) =>-         FromJSON (WithCursor c wrapped) where-    parseJSON (Object o) = checkError o >>-      WithCursor <$> o .:  "previous_cursor"-                 <*> o .:  "next_cursor"-                 <*> o .:  cursorKey (undefined :: c)-    parseJSON _ = mempty--data MediaData = MediaFromFile FilePath-               | MediaRequestBody FilePath RequestBody--#if !MIN_VERSION_twitter_types(0,5,0)-data ImageSizeType = ImageSizeType-    { imageWidth :: Int-    , imageHeight :: Int-    , imageType :: T.Text-    } deriving Show-instance FromJSON ImageSizeType where-    parseJSON (Object o) =-        ImageSizeType <$> o .:  "w"-                      <*> o .:  "h"-                      <*> o .:  "image_type"-    parseJSON v = fail $ "unknown value: " ++ show v+module Web.Twitter.Conduit.Types (+    TWToken (..),+    TWInfo (..),+    twitterOAuth,+    setCredential,+) where -data UploadedMedia = UploadedMedia-    { mediaId :: Integer-    , mediaSize :: Integer-    , mediaImage :: ImageSizeType-    } deriving Show-instance FromJSON UploadedMedia where-    parseJSON (Object o) =-        UploadedMedia <$> o .:  "media_id"-                      <*> o .:  "size"-                      <*> o .:  "image"-    parseJSON v = fail $ "unknown value: " ++ show v-#endif+import Data.Default+import Data.Typeable (Typeable)+import Network.HTTP.Conduit+import Web.Authenticate.OAuth -data Response responseType = Response-    { responseStatus :: Status-    , responseHeaders :: ResponseHeaders-    , responseBody :: responseType-    } deriving (Show, Eq, Typeable, Functor, Foldable, Traversable)+data TWToken = TWToken+    { twOAuth :: OAuth+    , twCredential :: Credential+    }+    deriving (Show, Read, Eq, Typeable)+instance Default TWToken where+    def = TWToken twitterOAuth (Credential []) -data TwitterError-    = FromJSONError String-    | TwitterErrorResponse Status ResponseHeaders [TwitterErrorMessage]-    | TwitterStatusError Status ResponseHeaders Value-    deriving (Show, Typeable, Eq)+data TWInfo = TWInfo+    { twToken :: TWToken+    , twProxy :: Maybe Proxy+    }+    deriving (Show, Read, Eq, Typeable)+instance Default TWInfo where+    def =+        TWInfo+            { twToken = def+            , twProxy = Nothing+            } -instance Exception TwitterError+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+        } --- | Twitter Error Messages+-- | set OAuth keys and Credentials to TWInfo. ----- see detail: <https://dev.twitter.com/docs/error-codes-responses>-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+-- >>> 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+        }
− Web/Twitter/Conduit/Types/Lens.hs
@@ -1,37 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE CPP #-}--module Web.Twitter.Conduit.Types.Lens-       ( TT.Response-       , responseStatus-       , responseBody-       , responseHeaders-       , TT.WithCursor-       , previousCursor-       , nextCursor-       , contents-#if !MIN_VERSION_twitter_types(0,5,0)-       , TT.ImageSizeType-       , imageWidth-       , imageHeight-       , imageType-       , TT.UploadedMedia-       , mediaId-       , mediaSize-       , mediaImage-#endif-       , TT.TwitterErrorMessage-       , twitterErrorMessage-       , twitterErrorCode-       ) where--import qualified Web.Twitter.Conduit.Types as TT-import Web.Twitter.Conduit.Types.TH--makeLenses ''TT.WithCursor-#if !MIN_VERSION_twitter_types(0,5,0)-makeLenses ''TT.ImageSizeType-makeLenses ''TT.UploadedMedia-#endif-makeLenses ''TT.Response-makeLenses ''TT.TwitterErrorMessage
− Web/Twitter/Conduit/Types/TH.hs
@@ -1,18 +0,0 @@-{-# LANGUAGE CPP #-}--module Web.Twitter.Conduit.Types.TH-       ( makeLenses-       ) where--import Control.Lens hiding (makeLenses)-import Language.Haskell.TH--#if MIN_VERSION_lens(4,4,0)-makeLenses :: Name -> Q [Dec]-makeLenses = makeLensesWith (lensRules & lensField .~ myFieldToDef)-  where-    myFieldToDef _ n = [TopName (mkName (nameBase n))]-#else-makeLenses :: Name -> Q [Dec]-makeLenses = makeLensesWith (defaultRules & lensField .~ Just)-#endif
− sample/Common.hs
@@ -1,62 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}--module Common where--import Web.Twitter.Conduit--import Web.Authenticate.OAuth as OA-import qualified Network.URI as URI-import Network.HTTP.Conduit-import qualified Data.Map as M-import qualified Data.ByteString.Char8 as S8-import qualified Data.CaseInsensitive as CI-import Control.Applicative-import Control.Monad.IO.Class-import Control.Monad.Base-import Control.Monad.Trans.Resource-import System.Environment-import Control.Monad.Logger-import Control.Lens--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--runTwitterFromEnv :: (MonadIO m, MonadBaseControl IO m) => TW (ResourceT m) a -> m a-runTwitterFromEnv task = do-    pr <- liftBase getProxyEnv-    (oa, cred) <- liftBase getOAuthTokens-    let env = (setCredential oa cred def) { twProxy = pr }-    runTW env task--runTwitterFromEnv' :: (MonadIO m, MonadBaseControl IO m) => TW (ResourceT (NoLoggingT m)) a -> m a-runTwitterFromEnv' = runNoLoggingT . runTwitterFromEnv
+ sample/LICENSE view
@@ -0,0 +1,26 @@+Copyright (c)2011-2014, Takahiro Himura++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ sample/Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple++main = defaultMain
+ sample/common/Common.hs view
@@ -0,0 +1,56 @@+{-# 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}
sample/fav.hs view
@@ -2,17 +2,20 @@  module Main where -import Control.Monad.IO.Class-import Web.Twitter.Conduit-import System.Environment import Common+import Web.Twitter.Conduit+ import Control.Lens+import System.Environment  main :: IO ()-main = runTwitterFromEnv' $ do-    [statusIdStr] <- liftIO getArgs+main = do+    [statusIdStr] <- getArgs+    twInfo <- getTWInfoFromEnv+    mgr <- newManager tlsManagerSettings     let sId = read statusIdStr-    targetStatus <- call $ showId sId-    liftIO . putStrLn $ "Favorite Tweet: " ++ targetStatus ^. to show-    res <- call $ favoritesCreate sId-    liftIO $ print res++    targetStatus <- call twInfo mgr $ statusesShowId sId+    putStrLn $ "Favorite Tweet: " ++ targetStatus ^. to show+    res <- call twInfo mgr $ favoritesCreate sId+    print res
sample/oauth_callback.hs view
@@ -7,22 +7,19 @@  module Main where -import Web.Scotty-import qualified Network.HTTP.Types as HT-import Web.Twitter.Conduit hiding (text, lookup)-import Web.Authenticate.OAuth (OAuth(..), Credential(..))-import qualified Web.Authenticate.OAuth as OA-import qualified Network.HTTP.Conduit as HTTP-import qualified Data.Text.Lazy as LT+import 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 Data.Monoid-import Data.IORef-import Control.Monad.IO.Class+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"@@ -33,10 +30,10 @@     consumerSecret <- getEnv "OAUTH_CONSUMER_SECRET"     return $         twitterOAuth-        { oauthConsumerKey = S8.pack consumerKey-        , oauthConsumerSecret = S8.pack consumerSecret-        , oauthCallback = Just $ S8.pack callback-        }+            { oauthConsumerKey = S8.pack consumerKey+            , oauthConsumerSecret = S8.pack consumerSecret+            , oauthCallback = Just $ S8.pack callback+            }  type OAuthToken = S.ByteString @@ -46,8 +43,8 @@ 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)+        let (res, newm) = M.updateLookupWithKey (\_ _ -> Nothing) k m+         in (newm, res)  storeCredential :: OAuthToken -> Credential -> IORef (M.Map OAuthToken Credential) -> IO () storeCredential k cred ioref =@@ -56,39 +53,40 @@ main :: IO () main = do     tokens <- getTokens+    mgr <- newManager tlsManagerSettings     putStrLn $ "browse URL: http://localhost:3000/signIn"-    scotty 3000 $ app tokens+    scotty 3000 $ app tokens mgr  makeMessage :: OAuth -> Credential -> S.ByteString makeMessage tokens (Credential cred) =-    S8.intercalate "\n"+    S8.intercalate+        "\n"         [ "export OAUTH_CONSUMER_KEY=\"" <> oauthConsumerKey tokens <> "\""         , "export OAUTH_CONSUMER_SECRET=\"" <> oauthConsumerSecret tokens <> "\""         , "export OAUTH_ACCESS_TOKEN=\"" <> fromMaybe "" (lookup "oauth_token" cred) <> "\""         , "export OAUTH_ACCESS_SECRET=\"" <> fromMaybe "" (lookup "oauth_token_secret" cred) <> "\""         ] -app :: OAuth -> ScottyM ()-app tokens = do+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 <- liftIO $ HTTP.withManager $ OA.getAccessToken tokens (OA.insert "oauth_verifier" oauthVerifier cred)+                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 <- liftIO $ HTTP.withManager $ OA.getTemporaryCredential tokens+        cred <- OA.getTemporaryCredential tokens mgr         case lookup "oauth_token" $ unCredential cred of             Just temporaryToken -> do                 liftIO $ storeCredential temporaryToken cred usersToken
sample/oauth_pin.hs view
@@ -1,5 +1,5 @@-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}  -- Example: --   $ export OAUTH_CONSUMER_KEY="your consumer key"@@ -8,18 +8,12 @@  module Main where -import Web.Twitter.Conduit hiding (lookup)-import Web.Authenticate.OAuth as OA-import Network.HTTP.Conduit-import qualified Data.Conduit as C import qualified Data.ByteString.Char8 as S8 import Data.Maybe-import Data.Monoid-import Control.Monad.Trans.Control-import Control.Monad.Trans.Resource-import Control.Monad.IO.Class import System.Environment import System.IO (hFlush, stdout)+import Web.Authenticate.OAuth as OA+import Web.Twitter.Conduit  getTokens :: IO OAuth getTokens = do@@ -27,22 +21,23 @@     consumerSecret <- getEnv "OAUTH_CONSUMER_SECRET"     return $         twitterOAuth-        { oauthConsumerKey = S8.pack consumerKey-        , oauthConsumerSecret = S8.pack consumerSecret-        , oauthCallback = Just "oob"-        }+            { oauthConsumerKey = S8.pack consumerKey+            , oauthConsumerSecret = S8.pack consumerSecret+            , oauthCallback = Just "oob"+            } -authorize :: (MonadBaseControl IO m, MonadResource m)-          => OAuth -- ^ OAuth Consumer key and secret-          -> Manager-          -> m Credential+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 = liftIO $ do+    getPIN url = do         putStrLn $ "browse URL: " ++ url         putStr "> what was the PIN twitter provided you with? "         hFlush stdout@@ -51,7 +46,8 @@ main :: IO () main = do     tokens <- getTokens-    Credential cred <- liftIO $ withManager $ authorize tokens+    mgr <- newManager tlsManagerSettings+    Credential cred <- authorize tokens mgr     print cred      S8.putStrLn . S8.intercalate "\n" $
sample/oslist.hs view
@@ -1,28 +1,31 @@ {-# LANGUAGE OverloadedStrings #-}+ module Main where -import qualified Data.Conduit as C+import Common+import Web.Twitter.Conduit++import Data.Conduit import qualified Data.Conduit.List as CL import qualified Data.Map as M-import Control.Monad.IO.Class import System.Environment -import Web.Twitter.Conduit-import Common- main :: IO ()-main = runTwitterFromEnv' $ do-    [screenName] <- liftIO getArgs+main = do+    [screenName] <- getArgs+    twInfo <- getTWInfoFromEnv+    mgr <- newManager tlsManagerSettings     let sn = ScreenNameParam screenName-    folids <- sourceWithCursor (followersIds sn) C.$$ CL.consume-    friids <- sourceWithCursor (friendsIds sn) C.$$ CL.consume +    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 -    liftIO $ putStrLn "one sided:"-    liftIO $ print os+    putStrLn "one sided:"+    print os -    liftIO $ putStrLn "both following:"-    liftIO $ print bo+    putStrLn "both following:"+    print bo
sample/post.hs view
@@ -1,18 +1,19 @@ {-# 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 Data.Monoid-import Control.Applicative-import Control.Monad.IO.Class-import Web.Twitter.Conduit import System.Environment-import Common  main :: IO ()-main = runTwitterFromEnv' $ do-    status <- T.concat . map T.pack <$> liftIO getArgs-    liftIO $ T.putStrLn $ "Post message: " <> status-    res <- call $ update status-    liftIO $ print res+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
sample/postWithMedia.hs view
@@ -1,15 +1,18 @@ {-# LANGUAGE OverloadedStrings #-}+ module Main where -import qualified Data.Text as T-import Control.Monad.IO.Class+import Common import Web.Twitter.Conduit++import qualified Data.Text as T import System.Environment-import Common  main :: IO ()-main = runTwitterFromEnv' $ do-    [status, filepath] <- liftIO getArgs-    liftIO $ putStrLn $ "Post message: " ++ status-    res <- call $ updateWithMedia (T.pack status) (MediaFromFile filepath)-    liftIO $ print res+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
sample/postWithMultipleMedia.hs view
@@ -1,27 +1,32 @@+{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-}+ module Main where -import qualified Data.Text as T+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-import Control.Lens-import Control.Monad.IO.Class-import Control.Monad-import Common  main :: IO ()-main = runTwitterFromEnv' $ do-    (status:filepathList) <- liftIO getArgs-    when (length filepathList > 4) $ liftIO $ do+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-        liftIO $ putStrLn $ "Upload media: " ++ filepath-        ret <- call $ mediaUpload (MediaFromFile filepath)-        liftIO $ putStrLn $ "Upload completed: media_id: " ++ ret ^. mediaId . to show ++ ", filepath: " ++ filepath+        putStrLn $ "Upload media: " ++ filepath+        ret <- call twInfo mgr $ mediaUpload (MediaFromFile filepath)+        putStrLn $ "Upload completed: media_id: " ++ ret ^. uploadedMediaId . to show ++ ", filepath: " ++ filepath         return ret-    liftIO $ putStrLn $ "Post message: " ++ status-    res <- call $ update (T.pack status) & mediaIds ?~ (uploadedMediaList ^.. traversed .  mediaId)-    liftIO $ print res+    putStrLn $ "Post message: " ++ status+    res <- call twInfo mgr $ statusesUpdate (T.pack status) & #media_ids ?~ (uploadedMediaList ^.. traversed . uploadedMediaId)+    print res
sample/search.hs view
@@ -1,20 +1,24 @@ {-# LANGUAGE OverloadedStrings #-}-module Main where -import Control.Monad.IO.Class-import System.Environment+module Main where -import Web.Twitter.Conduit import Common-import qualified Data.Text as T+import Web.Twitter.Conduit+import Web.Twitter.Types.Lens+ import Control.Lens+import qualified Data.Text as T+import System.Environment  main :: IO ()-main = runTwitterFromEnv' $ do-    [keyword] <- liftIO getArgs+main = do+    [keyword] <- getArgs -    res <- call . search $ T.pack keyword+    twInfo <- getTWInfoFromEnv+    mgr <- newManager tlsManagerSettings++    res <- call twInfo mgr $ search $ T.pack keyword     let metadata = res ^. searchResultSearchMetadata-    liftIO . putStrLn $ "search completed in: " ++ metadata ^. searchMetadataCompletedIn . to show-    liftIO . putStrLn $ "search result max id: " ++ metadata ^. searchMetadataMaxId . to show-    liftIO . print $ res ^. searchResultStatuses+    putStrLn $ "search completed in: " ++ metadata ^. searchMetadataCompletedIn . to show+    putStrLn $ "search result max id: " ++ metadata ^. searchMetadataMaxId . to show+    print $ res ^. searchResultStatuses
+ sample/searchSource.hs view
@@ -0,0 +1,26 @@+{-# 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
sample/simple.hs view
@@ -1,64 +1,69 @@-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE OverloadedStrings #-}  module Main where  import Web.Twitter.Conduit import Web.Twitter.Types.Lens+ import Control.Lens-import Web.Authenticate.OAuth (OAuth(..), Credential(..))-import qualified Web.Authenticate.OAuth as OA-import Network.HTTP.Conduit-import qualified Data.Conduit as C+import qualified Data.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 qualified Data.ByteString.Char8 as B8-import Data.Default-import Control.Monad.Logger-import Control.Monad.Trans.Control-import Control.Monad.Trans.Resource-import Control.Monad.IO.Class import System.IO (hFlush, stdout)+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."-    }+tokens =+    twitterOAuth+        { oauthConsumerKey = error "You MUST specify oauthConsumerKey parameter."+        , oauthConsumerSecret = error "You MUST specify oauthConsumerSecret parameter."+        } -authorize :: (MonadBaseControl IO m, MonadResource m)-          => OAuth -- ^ OAuth Consumer key and secret-          -> (String -> m String) -- ^ PIN prompt-          -> Manager-          -> m Credential+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 -withCredential :: (MonadLogger m, MonadBaseControl IO m, MonadIO m) => TW (ResourceT m) a -> m a-withCredential task = do-    cred <- liftIO $ withManager $ \mgr -> authorize tokens getPIN mgr-    let env = setCredential tokens cred def-    runTW env task+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 = liftIO $ do+    getPIN url = do         putStrLn $ "browse URL: " ++ url         putStr "> what was the PIN twitter provided you with? "         hFlush stdout         getLine  main :: IO ()-main = runNoLoggingT . withCredential $ do-    liftIO . putStrLn $ "# your home timeline (up to 100 tweets):"-    sourceWithMaxId homeTimeline-        C.$= CL.isolate 100-        C.$$ CL.mapM_ $ \status -> liftIO $ do-            T.putStrLn $ T.concat [ T.pack . show $ status ^. statusId-                                  , ": "-                                  , status ^. statusUser . userScreenName-                                  , ": "-                                  , status ^. statusText-                                  ]+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+                            ]+                )
+ sample/twitter-conduit-sample.cabal view
@@ -0,0 +1,208 @@+name:              twitter-conduit-sample+version:           0.1+license:           BSD3+license-file:      LICENSE+author:            Takahiro HIMURA+maintainer:        Takahiro HIMURA <taka@himura.jp>+synopsis:          Twitter API package with conduit interface and Streaming API support.+category:          Web, Conduit+stability:         Experimental+cabal-version:     >= 1.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
− sample/unfav.hs
@@ -1,17 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Main where--import Control.Monad.IO.Class-import Web.Twitter.Conduit-import System.Environment-import Common-import Control.Lens--main :: IO ()-main = runTwitterFromEnv' $ do-    [statusIdStr] <- liftIO getArgs-    let sId = read statusIdStr-    targetStatus <- call $ showId sId-    liftIO . putStrLn $ "Unfavorite Tweet: " ++ targetStatus ^. to show-    res <- call $ favoritesDestroy sId-    liftIO $ print res
sample/userstream.hs view
@@ -1,25 +1,23 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE PatternGuards #-} -import qualified Data.Conduit as C-import qualified Data.Conduit.List as CL-import qualified Data.Text.IO as T-import qualified Data.Text as T-import System.Process+import Common+import Web.Twitter.Conduit+import Web.Twitter.Types.Lens++import Control.Lens import Control.Monad import Control.Monad.IO.Class-import Control.Applicative-import System.FilePath-import System.Directory+import Control.Monad.Trans.Resource import Data.Conduit import qualified Data.Conduit.Binary as CB-import Network.HTTP.Conduit--import Web.Twitter.Conduit-import Web.Twitter.Types.Lens-import Common-import Control.Lens+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@@ -33,35 +31,43 @@ iconPath = (</> "icons") <$> confdir >>= ensureDirectoryExist  main :: IO ()-main = runTwitterFromEnv' $ do-    src <- stream userstream-    src C.$$+- CL.mapM_ (^! act (liftIO . printTL))+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-                        ]+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 (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+    | (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 ()@@ -69,15 +75,20 @@     let ic = maybe [] (\i -> ["-i", i]) icon     void $ rawSystem "notify-send" $ [T.unpack header, T.unpack content] ++ ic -fetchIcon :: String -- ^ screen name-          -> String -- ^ icon url-          -> IO String+fetchIcon ::+    -- | 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 $ withManager $ \mgr -> do-        req <- liftIO $ parseUrl url-        body <- http req mgr-        responseBody body $$+- CB.sinkFile 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
tests/ApiSpec.hs view
@@ -1,28 +1,38 @@-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE OverloadedStrings #-}  module ApiSpec where +import Control.Lens import Data.Conduit import qualified Data.Conduit.List as CL-import Web.Twitter.Conduit (call, sourceWithCursor)+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.Types.Lens+import Web.Twitter.Conduit.Lens import qualified Web.Twitter.Conduit.Parameters as Param import Web.Twitter.Types.Lens-import Control.Lens-import TestUtils -import Test.Hspec+twInfo :: TWInfo+twInfo = unsafePerformIO getTWInfo -import System.IO.Unsafe+mgr :: Manager+mgr = unsafePerformIO $ newManager tlsManagerSettings+{-# NOINLINE mgr #-}  self :: User-self = unsafePerformIO . run . call $ accountVerifyCredentials+self = unsafePerformIO $ call twInfo mgr $ accountVerifyCredentials+{-# NOINLINE self #-}  spec :: Spec spec = do     unit+ #ifdef RUN_INTEGRATED_TEST     integrated #endif@@ -32,28 +42,90 @@  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 &amp; 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 <- run . call $ friendsIds (Param.ScreenNameParam "thimura")+            res <- call twInfo mgr $ friendsIds (Param.ScreenNameParam "thimura")             res ^. contents . to length `shouldSatisfy` (> 0)          it "iterate with sourceWithCursor" $ do-            friends <- run $ do-                let src = sourceWithCursor $ friendsIds (Param.ScreenNameParam "thimura")-                src $$ CL.consume+            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 <- run . call $ listsMembers (Param.ListNameParam "thimura/haskell")+            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 = run . call $ listsMembers (Param.ListNameParam "thimura/haskell_ne")+            let action = call twInfo mgr $ listsMembers (Param.ListNameParam "thimura/haskell_ne")             action `shouldThrow` anyException          it "iterate with sourceWithCursor" $ do-            members <- run $ do-                let src = sourceWithCursor $ listsMembers (Param.ListNameParam "thimura/haskell")-                src $$ CL.consume+            let src = sourceWithCursor twInfo mgr $ listsMembers (Param.ListNameParam "thimura/haskell")+            members <- runConduit $ src .| CL.consume             members ^.. traversed . userScreenName `shouldContain` ["Hackage"]
+ tests/BaseSpec.hs view
@@ -0,0 +1,89 @@+{-# 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
− tests/StatusSpec.hs
@@ -1,87 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE CPP #-}--module StatusSpec where--import Data.Conduit-import qualified Data.Conduit.List as CL-import Web.Twitter.Conduit (call, accountVerifyCredentials, sourceWithMaxId)-import Web.Twitter.Conduit.Status as Status-import qualified Web.Twitter.Conduit.Parameters as Param-import Web.Twitter.Types.Lens-import Control.Lens-import TestUtils--import Test.Hspec--import System.IO.Unsafe--self :: User-self = unsafePerformIO . run . call $ accountVerifyCredentials--spec :: Spec-spec = do-    unit-#ifdef RUN_INTEGRATED_TEST-    integrated-#endif--unit :: Spec-unit = return ()--integrated :: Spec-integrated = do-    describe "mentionsTimeline" $ do-        it "returns the 20 most resent mentions for user" $ do-            res <- run $ call 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 <- run . call $ 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 <- run . call-                   $ userTimeline (Param.ScreenNameParam "thimura")-                   & Param.count ?~ 100 & Param.includeRts ?~ True-            res `shouldSatisfy` (anyOf (folded . statusRetweetedStatus . _Just . statusUser . userScreenName) (/= "thimura"))-        it "iterate with sourceWithMaxId" $ do-            tl <- run $ do-                let src = sourceWithMaxId $ userTimeline (Param.ScreenNameParam "thimura") & Param.count ?~ 200-                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 <- run $ call homeTimeline-            length res `shouldSatisfy` (> 0)--    describe "showId" $ do-        it "works for the known tweets" $ do-            res <- run . call $ showId 477833886768959488-            res ^. statusId `shouldBe` 477833886768959488-            res ^. statusText `shouldBe` "真紅かわいいはアレセイア"-            res ^. statusCreatedAt `shouldBe` "Sat Jun 14 15:24:10 +0000 2014"-            res ^. statusUser . userScreenName `shouldBe` "thimura"--    describe "update & destroyId" $ do-        it "posts new tweet and destroy it" $ do-            res1 <- run . call $ update "おまえの明日が、今日よりもずっと、楽しい事で溢れているようにと、祈っているよ"-            res1 ^. statusUser . userScreenName `shouldBe` self ^. userScreenName--            res2 <- run . call $ destroyId (res1 ^. statusId)-            res2 ^. statusId `shouldBe` res1 ^. statusId--    describe "lookup" $ do-        it "works for the known tweets" $ do-            res <- run . call $ Status.lookup [438691466345340928, 477757405942411265]-            length res `shouldSatisfy` (== 2)-            (res !! 0) ^. statusId `shouldBe` 438691466345340928-            (res !! 1) ^. statusId `shouldBe` 477757405942411265
tests/TestUtils.hs view
@@ -1,25 +1,10 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE CPP #-} -module TestUtils (runTwitterFromEnv, run) where--import Web.Twitter.Conduit+module TestUtils (getTWInfo) where -import Web.Authenticate.OAuth as OA-import qualified Network.URI as URI-import Network.HTTP.Conduit-import qualified Data.Map as M import qualified Data.ByteString.Char8 as S8-import qualified Data.CaseInsensitive as CI-import Control.Applicative-import Control.Monad.IO.Class-import Control.Monad.Base-import Control.Monad.Trans.Resource import System.Environment-import Control.Monad.Logger-import Control.Lens+import Web.Twitter.Conduit  getOAuthTokens :: IO (OAuth, Credential) getOAuthTokens = do@@ -27,42 +12,21 @@     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)-            ]+    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--runTwitterFromEnv :: (MonadIO m, MonadBaseControl IO m) => TW (ResourceT m) a -> m a-runTwitterFromEnv task = do-    pr <- liftBase getProxyEnv-    (oa, cred) <- liftBase getOAuthTokens-    let env = (setCredential oa cred def) { twProxy = pr }-    runTW env task--#ifdef USE_DEBUG_OUTPUT-run :: (MonadIO m, MonadBaseControl IO m) => TW (ResourceT (LoggingT m)) a -> m a-run = runStderrLoggingT . runTwitterFromEnv-#else-run :: (MonadIO m, MonadBaseControl IO m) => TW (ResourceT (NoLoggingT m)) a -> m a-run = runNoLoggingT . runTwitterFromEnv-#endif+getTWInfo :: IO TWInfo+getTWInfo = do+    (oa, cred) <- getOAuthTokens+    return $ setCredential oa cred def
tests/doctests.hs view
@@ -1,19 +1,12 @@ module Main where -import Test.DocTest-import System.Directory-import System.FilePath-import Data.List-import Control.Monad-import Control.Applicative+import Build_doctests (flags, module_sources, pkgs)+import Data.Foldable (traverse_)+import Test.DocTest (doctest) +main :: IO () main = do-  -- sources <- findSources "Web"-  doctest $ args -- ++ sources+    traverse_ putStrLn args -- optionally print arguments+    doctest args   where-    args = [ "-i."-           , "-idist/build/autogen"-           , "-optP-include"-           , "-optPdist/build/autogen/cabal_macros.h"-           , "Web/Twitter/Conduit.hs"-           ]+    args = flags ++ pkgs ++ module_sources
− tests/hlint.hs
@@ -1,12 +0,0 @@-module Main where--import Control.Monad-import Language.Haskell.HLint-import System.Environment-import System.Exit--main :: IO ()-main = do-  args <- getArgs-  hints <- hlint $ ["Web", "--cpp-define=HLINT", "--cpp-ansi", "--cpp-file=dist/build/autogen/cabal_macros.h"] ++ args-  unless (null hints) exitFailure
twitter-conduit.cabal view
@@ -1,272 +1,138 @@-name:              twitter-conduit-version:           0.0.5.8-license:           BSD3-license-file:      LICENSE-author:            HATTORI Hiroki, Hideyuki Tanaka, Takahiro HIMURA-maintainer:        Takahiro HIMURA <taka@himura.jp>-synopsis:          Twitter API package with conduit interface and Streaming API support.-category:          Web, Conduit-stability:         Experimental-cabal-version:     >= 1.8-build-type:        Simple-homepage:          https://github.com/himura/twitter-conduit+cabal-version:      1.24+name:               twitter-conduit+version:            0.6.1+license:            BSD3+license-file:       LICENSE+maintainer:         Takahiro HIMURA <taka@himura.jp>+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 <https://dev.twitter.com/>).-  .-  This package uses the http-conduit package for accessing the Twitter API (see <http://hackage.haskell.org/package/http-conduit>).-  This package also depends on the twitter-types package (see <http://hackage.haskell.org/package/twitter-types>).-  .-  You can find basic examples in the <https://github.com/himura/twitter-conduit/tree/master/sample> directory.-  .-  This package is under development. If you find something that has not been implemented yet, please send a pull request or open an issue on GitHub.+    This package provides bindings to Twitter's APIs (see <https://dev.twitter.com/>).+    .+    This package uses the http-conduit package for accessing the Twitter API (see <http://hackage.haskell.org/package/http-conduit>).+    This package also depends on the twitter-types package (see <http://hackage.haskell.org/package/twitter-types>).+    .+    You can find basic examples in the <https://github.com/himura/twitter-conduit/tree/master/sample> directory.+    .+    This package is under development. If you find something that has not been implemented yet, please send a pull request or open an issue on GitHub. +category:           Web, Conduit+build-type:         Custom extra-source-files:-  .gitignore-  .travis.yml-  README.md-  sample/*.hs+    .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--flag network-uri-  description: Get Network.URI from the network-uri package-  default: True--flag lens-aeson-  description: Get Data.Aeson.Lens from the lens-aeson package-  default: True--flag build-samples-  description: build samples-  default: False+    type:     git+    location: git://github.com/himura/twitter-conduit.git -flag use-debug-output-  description: use debug output when running testsuites-  default: False+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+    description: use debug output when running testsuites+    default:     False  library-  ghc-options: -Wall--  build-depends:-      base >= 4.5 && < 5-    , transformers >= 0.2.2-    , template-haskell-    , lens >= 4.0 && < 4.5-    , authenticate-oauth >= 1.3-    , resourcet >= 1.0-    , conduit >= 1.1-    , conduit-extra >= 1.1-    , monad-logger-    , http-types-    , http-conduit >= 2.0 && < 2.2-    , http-client >= 0.3-    , aeson >= 0.7.0.5-    , attoparsec >= 0.10-    , data-default >= 0.3-    , bytestring >= 0.9-    , text >= 0.11-    , containers-    , time-    , twitter-types >= 0.2--  if flag(lens-aeson)-    build-depends: lens-aeson >= 1--  exposed-modules:-    Web.Twitter.Conduit-    Web.Twitter.Conduit.Types-    Web.Twitter.Conduit.Types.Lens-    Web.Twitter.Conduit.Types.TH-    Web.Twitter.Conduit.Base-    Web.Twitter.Conduit.Api-    Web.Twitter.Conduit.Monad-    Web.Twitter.Conduit.Stream-    Web.Twitter.Conduit.Status-    Web.Twitter.Conduit.Request-    Web.Twitter.Conduit.Parameters-    Web.Twitter.Conduit.Parameters.Internal-    Web.Twitter.Conduit.Parameters.TH--  other-modules:--test-suite hlint-  type: exitcode-stdio-1.0-  main-is: hlint.hs-  hs-source-dirs: tests--  build-depends:-      base-    , hlint >= 1.7--test-suite doctests-  type: exitcode-stdio-1.0-  main-is: doctests.hs-  hs-source-dirs: tests--  build-depends:-      base-    , filepath-    , directory-    , doctest--test-suite spec_main-  type: exitcode-stdio-1.0-  main-is: spec_main.hs-  hs-source-dirs: tests, .-  if flag(use-debug-output)-    CPP-Options: -DUSE_DEBUG_OUTPUT--  if flag(run-integrated-test)-    CPP-Options: -DRUN_INTEGRATED_TEST--  build-depends:-      base >= 4.5 && < 5-    , template-haskell-    , transformers-base-    , transformers-    , monad-control-    , lens-    , bytestring-    , time-    , text-    , aeson-    , attoparsec >= 0.10-    , data-default-    , resourcet-    , conduit-    , conduit-extra >= 1.1-    , http-types-    , http-conduit-    , http-client-    , monad-logger-    , authenticate-oauth-    , case-insensitive-    , containers-    , hspec-    , twitter-types >= 0.4.0--  if flag(network-uri)-    build-depends: network-uri >= 2.6-  else-    build-depends: network < 2.6--  if flag(lens-aeson)-    build-depends: lens-aeson >= 1--  other-modules:-    Spec-    ApiSpec-    StatusSpec-    TestUtils--executable simple-  main-is: simple.hs-  hs-source-dirs: sample/+    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 -  if !flag(build-samples)-    buildable: False-  else+    default-language: Haskell2010+    ghc-options:      -Wall     build-depends:-        base >= 4.5 && < 5-      , transformers-base-      , transformers-      , monad-control-      , lens-      , bytestring-      , text-      , data-default-      , resourcet-      , conduit-      , http-conduit-      , monad-logger-      , authenticate-oauth-      , twitter-conduit-      , twitter-types--executable userstream-  main-is: userstream.hs-  hs-source-dirs: sample/+        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 -  if !flag(build-samples)-    buildable: False-  else+test-suite doctests+    type:             exitcode-stdio-1.0+    main-is:          doctests.hs+    hs-source-dirs:   tests+    default-language: Haskell2010     build-depends:-        base >= 4.5 && < 5-      , containers-      , transformers-base-      , transformers-      , monad-control-      , bytestring-      , text-      , filepath-      , directory-      , process-      , case-insensitive-      , lens-      , aeson-      , data-default-      , resourcet-      , conduit-      , conduit-extra-      , http-conduit-      , monad-logger-      , authenticate-oauth-      , twitter-conduit-      , twitter-types--  if flag(network-uri)-    build-depends: network-uri >= 2.6-  else-    build-depends: network < 2.6+        base,+        doctest -executable oauth_callback-  main-is: oauth_callback.hs-  hs-source-dirs: sample/+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 -  if !flag(build-samples)-    buildable: False-  else+    default-language:   Haskell2010     build-depends:-        base >= 4.5 && < 5-      , containers-      , transformers-base-      , transformers-      , monad-control-      , bytestring-      , text-      , resourcet-      , conduit-      , http-types-      , http-conduit-      , authenticate-oauth-      , twitter-conduit-      , scotty >= 0.7--executable oauth_pin-  main-is: oauth_pin.hs-  hs-source-dirs: sample/+        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(build-samples)-    buildable: False-  else-    build-depends:-        base >= 4.5 && < 5-      , containers-      , transformers-base-      , transformers-      , monad-control-      , bytestring-      , text-      , resourcet-      , conduit-      , http-types-      , http-conduit-      , authenticate-oauth-      , twitter-conduit+    if flag(run-integrated-test)+        cpp-options: -DRUN_INTEGRATED_TEST