packages feed

twitter-enumerator 0.0.2 → 0.0.3

raw patch · 7 files changed

+295/−110 lines, 7 filesdep +tls-extradep ~http-enumeratorsetup-changed

Dependencies added: tls-extra

Dependency ranges changed: http-enumerator

Files

Setup.hs view
@@ -1,2 +1,3 @@+#!/usr/bin/env runhaskell import Distribution.Simple main = defaultMain
Web/Twitter/Enumerator/Api.hs view
@@ -1,16 +1,25 @@+{-# LANGUAGE OverloadedStrings #-}+ module Web.Twitter.Enumerator.Api        ( api        , endpoint+       , endpointSearch+       , UserParam(..)+       , ListParam(..)+       , mkUserParam+       , mkListParam        ) where  import Web.Twitter.Enumerator.Types import Web.Twitter.Enumerator.Monad+import Web.Twitter.Enumerator.Utils  import Network.HTTP.Enumerator import qualified Network.HTTP.Types as HT import Data.Enumerator (Iteratee, throwError, liftTrans)  import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as B8  import Control.Monad.Trans.Class import Control.Monad.IO.Class (MonadIO (liftIO))@@ -18,13 +27,17 @@ endpoint :: String endpoint = "https://api.twitter.com/1/" -api :: ByteString -- ^ HTTP request method (GET or POST)+endpointSearch :: String+endpointSearch = "http://search.twitter.com/"++api :: Bool -- ^ OAuth required?+    -> ByteString -- ^ HTTP request method (GET or POST)     -> String -- ^ API Resource URL     -> HT.Query -- ^ Query     -> Iteratee ByteString IO a     -> Iteratee ByteString TW a-api m url query iter = do-  req <- lift $ apiRequest m url query+api toOAuth m url query iter = do+  req <- lift $ apiRequest toOAuth m url query   httpMgr req (handleError iter)   where     handleError iter' st@(HT.Status sc _) _ =@@ -41,10 +54,28 @@   mgr <- lift getManager   liftTrans $ http req iterf mgr --apiRequest :: ByteString -> String -> HT.Query -> TW (Request IO)-apiRequest m uri query = do+apiRequest :: Bool -> ByteString -> String -> HT.Query -> TW (Request IO)+apiRequest toOAuth m uri query = do   p <- getProxy   req <- liftIO $ parseUrl uri >>= \r ->     return $ r { method = m, queryString = query, proxy = p }-  signOAuthTW req+  if toOAuth then signOAuthTW req+             else return req++data UserParam = UserIdParam UserId | ScreenNameParam String+               deriving (Show, Eq)+data ListParam = ListIdParam Integer | ListNameParam String+               deriving (Show, Eq)++mkUserParam :: UserParam -> HT.Query+mkUserParam (UserIdParam uid) =  [("user_id", toMaybeByteString uid)]+mkUserParam (ScreenNameParam sn) = [("screen_name", Just . B8.pack $ sn)]++mkListParam :: ListParam -> HT.Query+mkListParam (ListIdParam lid) =  [("list_id", toMaybeByteString lid)]+mkListParam (ListNameParam listname) =+  [("slug", Just . B8.pack $ lstName),+   ("owner_screen_name", Just . B8.pack $ screenName)]+  where+    (screenName, ln) = span (/= '/') listname+    lstName = drop 1 ln
Web/Twitter/Enumerator/Fetch.hs view
@@ -1,9 +1,9 @@ {-# LANGUAGE OverloadedStrings #-}  module Web.Twitter.Enumerator.Fetch-       ( QueryUser(..)-       , QueryList(..)-+       (+         UserParam (..)+       , ListParam (..)        -- * Timelines        , statusesHomeTimeline        , statusesMentions@@ -22,7 +22,7 @@        , statusesShowId         -- * Search-       -- , search+       , search         -- * Direct Messages        -- , directMessages@@ -88,33 +88,29 @@ import qualified Data.Text as T import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as B8+import Data.Maybe import Control.Monad.Trans.Class-import Control.Monad.IO.Class (MonadIO (liftIO)) import Control.Applicative  import qualified Data.Map as M -data QueryUser = QUserId UserId | QScreenName String-               deriving (Show, Eq)-data QueryList = QListId Integer | QListName String-               deriving (Show, Eq)-- apiGet :: FromJSON a => String -> HT.Query -> Iteratee a IO b -> TW b-apiGet uri query iter = run_ $ api "GET" uri query (handleParseError iter')+apiGet uri query iter = run_ $ api True "GET" uri query (handleParseError iter')   where iter' = enumJSON =$ EL.map fromJSON' =$ skipNothing =$ iter  statuses :: (FromJSON a, Show a) => String -> HT.Query -> Enumerator a TW b-statuses uri query = apiWithPages furi query 1+statuses uri query = apiWithPages True furi query 1   where furi = endpoint ++ "statuses/" ++ uri -apiWithPages :: (FromJSON a, Show a) => String -> HT.Query -> Integer -> Enumerator a TW b-apiWithPages uri query initPage =+apiWithPages :: (FromJSON a, Show a) => Bool -> String -> HT.Query -> Integer -> Enumerator a TW b+apiWithPages isStatuses uri query initPage =+-- isStatuses = True:  normal GET/statuses APIs with OAuth+-- isStatuses = False: search API   checkContinue1 go initPage   where     go loop page k = do       let query' = insertQuery "page" (toMaybeByteString page) query-      res <- lift $ run_ $ api "GET" uri query' (handleParseError (enumJSON =$ iterPageC))+      res <- lift $ run_ $ api isStatuses "GET" uri query' (handleParseError (enumJSON =$ (if isStatuses then iterPageC else iterPageCSearch)))       case res of         Just [] -> k EOF         Just xs -> k (Chunks xs) >>== loop (page + 1)@@ -127,6 +123,13 @@     Just v -> return . fromJSON' $ v     Nothing -> return Nothing +iterPageCSearch :: (Monad m, FromJSON a) => Iteratee Value m (Maybe [a])+iterPageCSearch = do+  ret <- EL.head+  case ret of+    Just v -> return . fromJSONSearch' $ v+    Nothing -> return Nothing+ insertQuery :: ByteString -> Maybe ByteString -> HT.Query -> HT.Query insertQuery key value = mk   where mk = M.toList . M.insert key value . M.fromList@@ -171,36 +174,26 @@ statusesShowId :: StatusId -> HT.Query -> TW Status statusesShowId status_id query = apiGet (endpoint ++ "statuses/show/" ++ show status_id ++ ".json") query EL.head_ -mkQueryUser :: QueryUser -> HT.Query-mkQueryUser (QUserId uid) =  [("user_id", toMaybeByteString uid)]-mkQueryUser (QScreenName sn) = [("screen_name", Just . B8.pack $ sn)]--mkQueryList :: QueryList -> HT.Query-mkQueryList (QListId lid) =  [("list_id", toMaybeByteString lid)]-mkQueryList (QListName listname) =-  [("slug", Just . B8.pack $ lstName),-   ("owner_screen_name", Just . B8.pack $ screenName)]-  where-    (screenName, ln) = span (/= '/') listname-    lstName = drop 1 ln+search :: String -> Enumerator SearchStatus TW a+search q = apiWithPages False (endpointSearch ++ "search.json") query 1+  where query = [("q", Just . B8.pack $ q)] -friendsIds, followersIds :: QueryUser -> Enumerator UserId TW a-friendsIds q = apiCursor (endpoint ++ "friends/ids.json") (mkQueryUser q) "ids" (-1)-followersIds q = apiCursor (endpoint ++ "followers/ids.json") (mkQueryUser q) "ids" (-1)+friendsIds, followersIds :: UserParam -> Enumerator UserId TW a+friendsIds q = apiCursor (endpoint ++ "friends/ids.json") (mkUserParam q) "ids" (-1)+followersIds q = apiCursor (endpoint ++ "followers/ids.json") (mkUserParam q) "ids" (-1) -usersShow :: QueryUser -> TW User-usersShow q = apiGet (endpoint ++ "users/show.json") (mkQueryUser q) EL.head_+usersShow :: UserParam -> TW User+usersShow q = apiGet (endpoint ++ "users/show.json") (mkUserParam q) EL.head_ -listsAll :: QueryUser -> Enumerator List TW a-listsAll q = apiCursor (endpoint ++ "lists/all.json") (mkQueryUser q) "" (-1)+listsAll :: UserParam -> Enumerator List TW a+listsAll q = apiCursor (endpoint ++ "lists/all.json") (mkUserParam q) "" (-1) -listsMembers :: QueryList -> Enumerator User TW a-listsMembers q = apiCursor (endpoint ++ "lists/members.json") (mkQueryList q) "users" (-1)+listsMembers :: ListParam -> Enumerator User TW a+listsMembers q = apiCursor (endpoint ++ "lists/members.json") (mkListParam q) "users" (-1)  data Cursor a =   Cursor   { cursorCurrent :: [a]-  , cursorPrev :: Maybe Integer   , cursorNext :: Maybe Integer   } deriving (Show, Eq) @@ -214,20 +207,12 @@ iterCursor :: (Monad m, FromJSON a) => T.Text -> Iteratee ByteString m (Maybe (Cursor a)) iterCursor key = enumLine =$ handleParseError (enumJSON =$ iterCursor' key) -handleParseError :: Monad m => Iteratee ByteString m b -> Iteratee ByteString m b-handleParseError iter = iter `catchError` hndl-  where-    getChunk = continue return-    hndl e = getChunk >>= \x -> case x of-      Chunks xs -> throwError $ PerserException e xs-      _ -> throwError $ PerserException e []- parseCursor :: FromJSON a => T.Text -> Value -> AE.Parser (Cursor a) parseCursor key (Object o) =   checkError o   <|>-  Cursor <$> o .: key <*> o .:? "previous_cursor" <*> o .:? "next_cursor"-parseCursor _ v@(Array _) = return $ Cursor (maybe [] id $ fromJSON' v) Nothing Nothing+  Cursor <$> o .: key <*> o .:? "next_cursor"+parseCursor _ v@(Array _) = return $ Cursor (fromMaybe [] $ fromJSON' v) Nothing parseCursor _ o = fail $ "Error at parseCursor: unknown object " ++ show o  apiCursor@@ -242,7 +227,7 @@   where     go loop cursor k = do       let query' = insertQuery "cursor" (toMaybeByteString cursor) query-      res <- lift $ run_ $ api "GET" uri query' (iterCursor cursorKey)+      res <- lift $ run_ $ api True "GET" uri query' (iterCursor cursorKey)       case res of         Just r -> do           let nextCur = cursorNext r@@ -259,10 +244,7 @@ apiIter iter = enumLine =$ handleParseError (enumJSON =$ EL.map fromJSON' =$ skipNothing =$ iter)  userstream :: Iteratee StreamingAPI IO a -> Iteratee ByteString TW a-userstream = api "GET" "https://userstream.twitter.com/2/user.json" [] . apiIter+userstream = api True "GET" "https://userstream.twitter.com/2/user.json" [] . apiIter  statusesFilter :: HT.Query -> Iteratee StreamingAPI IO a -> Iteratee ByteString TW a-statusesFilter query = api "GET" "https://stream.twitter.com/1/statuses/filter.json" query . apiIter--toMaybeByteString :: Show a => a -> Maybe ByteString-toMaybeByteString = Just . B8.pack . show+statusesFilter query = api True "GET" "https://stream.twitter.com/1/statuses/filter.json" query . apiIter
Web/Twitter/Enumerator/Post.hs view
@@ -2,30 +2,69 @@  module Web.Twitter.Enumerator.Post        ( statusesUpdate+       , statusesRetweetId -         -- * Friendship-         -- , friendshipCreate-         -- , friendshipDestroy+       -- * Friends & Followers+       , friendshipsCreate+       -- , friendshipDestroy -         -- * Favorites-         -- , favoritesCreate-         -- , favoritesDestroy+       -- * Favorites+       , favoritesCreate+       , favoritesDestroy -         -- * Lists-         -- , listsCreate-         -- , listsDestroy-         -- , listsUpdate-         -- , listsMembersCreate-         -- , listsMembersDestroy-       )-       where+       -- * Lists+       -- , listsCreate+       -- , listsDestroy+       -- , listsUpdate+       -- , listsMembersCreate+       -- , listsMembersDestroy +       -- * Deprecated+       , retweet+       , friendshipCreate++       ) where++import Data.Aeson hiding (Error)++import Web.Twitter.Enumerator.Types import Web.Twitter.Enumerator.Monad+import Web.Twitter.Enumerator.Utils import Web.Twitter.Enumerator.Api +import Data.Text (Text)+import qualified Data.Text.Encoding as T import Data.ByteString (ByteString) -import Data.Enumerator (Iteratee)+import qualified Network.HTTP.Types as HT+import Data.Enumerator (Iteratee, (=$), run_)+import qualified Data.Enumerator.List as EL -statusesUpdate :: ByteString -> Iteratee ByteString IO a -> Iteratee ByteString TW a-statusesUpdate tweet iter = api "POST" (endpoint ++ "statuses/update.json") [("status", Just tweet)] iter+apiPost :: FromJSON a => String -> HT.Query -> Iteratee a IO b -> TW b+apiPost uri query iter = run_ $ api True "POST" uri query (handleParseError iter')+  where iter' = enumJSON =$ EL.map fromJSON' =$ skipNothing =$ iter++statusesUpdate :: Text -> HT.Query -> TW Status+statusesUpdate tweet query = apiPost (endpoint ++ "statuses/update.json") q (debugEE =$ EL.head_)+  where q = ("status", Just . T.encodeUtf8 $ tweet):query++favoritesCreate :: StatusId -> HT.Query -> TW Status+favoritesCreate sid query = apiPost (endpoint ++ "favorites/create/" ++ show sid ++ ".json") query EL.head_++favoritesDestroy :: StatusId -> HT.Query -> TW Status+favoritesDestroy sid query = apiPost (endpoint ++ "favorites/destroy/" ++ show sid ++ ".json") query EL.head_++statusesRetweetId :: Integer -> HT.Query -> TW RetweetedStatus+statusesRetweetId tweetId query = apiPost (endpoint ++ "statuses/retweet/" ++ show tweetId ++ ".json") query EL.head_++friendshipsCreate :: UserParam -> HT.Query -> TW User+friendshipsCreate user query = apiPost (endpoint ++ "friendships/create.json") q EL.head_+  where q = mkUserParam user ++ query++{-# DEPRECATED retweet "'retweet' will be removed in future releases. Use 'statusesRetweetId' instead" #-}+retweet :: Integer -> Iteratee ByteString IO a -> Iteratee ByteString TW a+retweet tweetId = api True "POST" (endpoint ++ "statuses/retweet/" ++ show tweetId ++ ".json") []++{-# DEPRECATED friendshipCreate "'friendshipCreate' will be removed in future releases. Use 'friendshipsCreate' instead" #-}+friendshipCreate :: UserId -> Iteratee ByteString IO a -> Iteratee ByteString TW a+friendshipCreate uid = api True "POST" (endpoint ++ "friendships/create.json") [("user_id", toMaybeByteString uid)]
Web/Twitter/Enumerator/Types.hs view
@@ -4,17 +4,25 @@        ( TwitterException(..)        , DateString        , UserId+       , Friends        , URLString        , UserName        , StatusId+       , LanguageCode        , StreamingAPI(..)        , Status(..)+       , SearchStatus(..)        , RetweetedStatus(..)        , EventTarget(..)        , Event(..)        , Delete(..)        , User(..)        , List(..)+       , Entities(..)+       , Entity(..)+       , HashTagEntity(..)+       , UserEntity(..)+       , URLEntity(..)        , checkError        )        where@@ -30,16 +38,18 @@ import Control.Exception  data TwitterException = HTTPStatusCodeException HT.Status-                      | PerserException SomeException [ByteString]+                      | ParserException SomeException [ByteString]                       | TwitterErrorMessage T.Text Value                       deriving (Show, Typeable) instance Exception TwitterException -type DateString  = String-type UserId      = Integer-type URLString   = String-type UserName    = T.Text-type StatusId    = Integer+type DateString   = String+type UserId       = Integer+type Friends      = [UserId]+type URLString    = String+type UserName     = T.Text+type StatusId     = Integer+type LanguageCode = String  data StreamingAPI = SStatus Status                   | SRetweetedStatus RetweetedStatus@@ -64,7 +74,7 @@     SEvent <$> js <|>     SDelete <$> js <|>     SFriends <$> js <|>-    (return $ SUnknown v)+    return (SUnknown v)     where       js :: FromJSON a => Parser a       js = parseJSON v@@ -77,9 +87,11 @@   , statusText          :: T.Text   , statusSource        :: String   , statusTruncated     :: Bool+  , statusEntities      :: Maybe Entities   , statusInReplyTo     :: Maybe StatusId   , statusInReplyToUser :: Maybe UserId   , statusFavorite      :: Maybe Bool+  , statusRetweetCount  :: Maybe Integer   , statusUser          :: User   } deriving (Show, Eq) @@ -90,12 +102,34 @@            <*> o .:  "text"            <*> o .:  "source"            <*> o .:  "truncated"+           <*> o .:? "entities"            <*> o .:? "in_reply_to_status_id"            <*> o .:? "in_reply_to_user_id"-           <*> o .:? "favorite"+           <*> o .:? "favorited"+           <*> o .:? "retweet_count"            <*> o .:  "user"   parseJSON _ = mzero +data SearchStatus =+  SearchStatus+  { searchStatusCreatedAt     :: DateString+  , searchStatusId            :: StatusId+  , searchStatusText          :: T.Text+  , searchStatusSource        :: String+  , searchStatusUserId        :: UserId+  , searchStatusUserName      :: UserName+  } deriving (Show, Eq)++instance FromJSON SearchStatus where+  parseJSON (Object o) = checkError o <|>+    SearchStatus <$> o .:  "created_at"+                 <*> o .:  "id"+                 <*> o .:  "text"+                 <*> o .:  "source"+                 <*> o .:  "from_user_id"+                 <*> o .:  "from_user"+  parseJSON _ = mzero+ data RetweetedStatus =   RetweetedStatus   { rsCreatedAt       :: DateString@@ -103,19 +137,21 @@   , rsText            :: T.Text   , rsSource          :: String   , rsTruncated       :: Bool+  , rsEntities        :: Maybe Entities   , rsUser            :: User   , rsRetweetedStatus :: Status   } deriving (Show, Eq)  instance FromJSON RetweetedStatus where   parseJSON (Object o) = checkError o <|>-    RetweetedStatus <$> o .: "created_at"-                    <*> o .: "id"-                    <*> o .: "text"-                    <*> o .: "source"-                    <*> o .: "truncated"-                    <*> o .: "user"-                    <*> o .: "retweeted_status"+    RetweetedStatus <$> o .:  "created_at"+                    <*> o .:  "id"+                    <*> o .:  "text"+                    <*> o .:  "source"+                    <*> o .:  "truncated"+                    <*> o .:? "entities"+                    <*> o .:  "user"+                    <*> o .:  "retweeted_status"   parseJSON _ = mzero  data EventType = Favorite | Unfavorite@@ -131,7 +167,7 @@     ETUser <$> parseJSON v <|>     ETStatus <$> parseJSON v <|>     ETList <$> parseJSON v <|>-    (return $ ETUnknown v)+    return (ETUnknown v)   parseJSON _ = mzero  data Event =@@ -165,19 +201,21 @@            <*> s .: "user_id"   parseJSON _ = mzero -type Friends = [UserId]- data User =   User   { userId              :: UserId   , userName            :: UserName   , userScreenName      :: String-  , userDescription     :: T.Text-  , userLocation        :: T.Text+  , userDescription     :: Maybe T.Text+  , userLocation        :: Maybe T.Text   , userProfileImageURL :: Maybe URLString   , userURL             :: Maybe URLString   , userProtected       :: Maybe Bool   , userFollowers       :: Maybe Int+  , userFriends         :: Maybe Int+  , userTweets          :: Maybe Int+  , userLangCode        :: Maybe LanguageCode+  , userCreatedAt       :: Maybe DateString   } deriving (Show, Eq)  instance FromJSON User where@@ -185,12 +223,16 @@     User <$> o .:  "id"          <*> o .:  "name"          <*> o .:  "screen_name"-         <*> o .:  "description"-         <*> o .:  "location"+         <*> o .:? "description"+         <*> o .:? "location"          <*> o .:? "profile_image_url"          <*> o .:? "url"          <*> o .:? "protected"          <*> o .:? "followers_count"+         <*> o .:? "friends_count"+         <*> o .:? "statuses_count"+         <*> o .:? "lang"+         <*> o .:? "created_at"   parseJSON _ = mzero  data List =@@ -206,11 +248,78 @@  instance FromJSON List where   parseJSON (Object o) = checkError o <|>-    List <$> o .: "id"-         <*> o .: "name"-         <*> o .: "full_name"-         <*> o .: "member_count"-         <*> o .: "subscriber_count"-         <*> o .: "mode"-         <*> o .: "user"+    List <$> o .:  "id"+         <*> o .:  "name"+         <*> o .:  "full_name"+         <*> o .:  "member_count"+         <*> o .:  "subscriber_count"+         <*> o .:  "mode"+         <*> o .:  "user"   parseJSON _ = mzero++data HashTagEntity =+  HashTagEntity+  { hashTagText :: T.Text -- ^ The Hashtag text+  } deriving (Show, Eq)++instance FromJSON HashTagEntity where+  parseJSON (Object o) =+    HashTagEntity <$> o .: "text"+  parseJSON _ = mzero++-- | The 'UserEntity' is just a wrapper around 'User' which is+--   a bit wasteful, and should probably be replaced by just+--   storing the id, name and screen name here.+data UserEntity = UserEntity User+                deriving (Show, Eq)++instance FromJSON UserEntity where+  parseJSON = (UserEntity <$>) . parseJSON++data URLEntity =+  URLEntity+  { ueURL      :: URLString -- ^ The URL that was extracted+  , ueExpanded :: URLString -- ^ The fully resolved URL (only for t.co links)+  , ueDisplay  :: T.Text    -- ^ Not a URL but a string to display instead of the URL (only for t.co links)+  } deriving (Show, Eq)++instance FromJSON URLEntity where+  parseJSON (Object o) =+    URLEntity <$> o .:  "url"+              <*> o .:  "expanded_url"+              <*> o .:  "display_url"+  parseJSON _ = mzero++-- | Entity handling.+data Entities =+  Entities+  { enHashTags     :: [Entity HashTagEntity]+  , enUserMentions :: [Entity UserEntity]+  , enURLs         :: [Entity URLEntity]+  } deriving (Show, Eq)++instance FromJSON Entities where+  parseJSON (Object o) =+    Entities <$> o .:  "hashtags"+             <*> o .:  "user_mentions"+             <*> o .:  "urls"+  parseJSON _ = mzero++-- | The character positions the Entity was extracted from+--+--   This is experimental implementation.+--   This may be replaced by more definite types.+type EntityIndices = [Int]++data Entity a =+  Entity+  { entityBody    :: a             -- ^ The detail information of the specific entity types (HashTag, URL, User)+  , entityIndices :: EntityIndices -- ^ The character positions the Entity was extracted from+  } deriving (Show, Eq)++instance FromJSON a => FromJSON (Entity a) where+  parseJSON v@(Object o) =+    Entity <$> parseJSON v+           <*> o .: "indices"+  parseJSON _ = mzero+
Web/Twitter/Enumerator/Utils.hs view
@@ -1,12 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+ module Web.Twitter.Enumerator.Utils        ( enumLine        , enumJSON        , skipNothing        , debugEE        , fromJSON'+       , fromJSONSearch'+       , toMaybeByteString+       , handleParseError        )        where +import Web.Twitter.Enumerator.Types+ import Data.Aeson hiding (Error) import Data.Aeson.Types (parseMaybe) import Data.Attoparsec.Enumerator (iterParser)@@ -14,6 +21,7 @@ import qualified Data.Enumerator.List as EL import qualified Data.Enumerator.Binary as EB import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as B8 import Data.Maybe import Control.Monad.IO.Class (MonadIO (liftIO)) @@ -30,6 +38,20 @@ fromJSON' :: FromJSON a => Value -> Maybe a fromJSON' = parseMaybe parseJSON +fromJSONSearch' :: FromJSON a => Value -> Maybe a+fromJSONSearch' (Object o) = parseMaybe (.: "results") o+fromJSONSearch' _          = Nothing+ enumJSON :: Monad m => E.Enumeratee ByteString Value m a enumJSON = E.sequence $ iterParser json +toMaybeByteString :: Show a => a -> Maybe ByteString+toMaybeByteString = Just . B8.pack . show++handleParseError :: Monad m => Iteratee ByteString m b -> Iteratee ByteString m b+handleParseError iter = iter `catchError` hndl+  where+    getChunk = continue return+    hndl e = getChunk >>= \x -> case x of+      Chunks xs -> throwError $ ParserException e xs+      _ -> throwError $ ParserException e []
twitter-enumerator.cabal view
@@ -1,5 +1,5 @@ name:              twitter-enumerator-version:           0.0.2+version:           0.0.3 license:           BSD3 license-file:      LICENSE author:            Takahiro HIMURA@@ -19,7 +19,7 @@ source-repository this   type: git   location: git://github.com/himura/twitter-enumerator.git-  tag: 0.0.2+  tag: 0.0.3  library   ghc-options: -Wall@@ -28,9 +28,10 @@       base >= 4 && < 5     , transformers >= 0.2 && < 0.3     , authenticate >= 0.9 && < 0.11+    , tls-extra >= 0.4 && < 0.4.3     , enumerator >= 0.4.14 && < 0.5     , http-types-    , http-enumerator >= 0.6.6+    , http-enumerator >= 0.6.6 && < 0.7.3     , aeson >= 0.3.2.2     , attoparsec >= 0.8.6.1     , attoparsec-enumerator