packages feed

reddit 0.1.1.0 → 0.2.0.0

raw patch · 22 files changed

+297/−298 lines, 22 filesdep +data-default-classdep +freedep +http-clientdep −data-defaultdep −http-conduitdep −stmdep ~aesondep ~api-builderdep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: data-default-class, free, http-client, http-client-tls

Dependencies removed: data-default, http-conduit, stm

Dependency ranges changed: aeson, api-builder, bytestring, http-types, network, text, time, unordered-containers, vector

API changes (from Hackage documentation)

- Reddit.Types.Reddit: instance (Functor m, Monad m) => Applicative (RedditT m)
- Reddit.Types.Reddit: instance Functor m => Functor (RedditT m)
- Reddit.Types.Reddit: unRedditT :: RedditT m a -> APIT (TVar RateLimits) RedditError m a
+ Reddit: defaultRedditOptions :: RedditOptions
+ Reddit: interpretIO :: MonadIO m => RedditState -> RedditT m a -> m (Either (APIError RedditError, Maybe (RedditT m a)) a)
+ Reddit: runResumeRedditWith :: MonadIO m => RedditOptions -> RedditT m a -> m (Either (APIError RedditError, Maybe (RedditT m a)) a)
+ Reddit.Actions.Message: replyMessage :: (Monad m, Thing a) => a -> Text -> RedditT m MessageID
+ Reddit.Actions.User: getUserPosts :: Monad m => Username -> RedditT m PostListing
+ Reddit.Actions.User: getUserPosts' :: Monad m => Options PostID -> Username -> RedditT m PostListing
+ Reddit.Types.Message: instance FromJSON (POSTWrapped MessageID)
+ Reddit.Types.Message: instance Ord MessageID
+ Reddit.Types.Message: instance Ord MessageKind
+ Reddit.Types.Reddit: FailWith :: APIError RedditError -> RedditF m a
+ Reddit.Types.Reddit: Nest :: RedditT m b -> (Either (APIError RedditError) b -> a) -> RedditF m a
+ Reddit.Types.Reddit: NestResuming :: RedditT m b -> (Either (APIError RedditError, Maybe (RedditT m b)) b -> a) -> RedditF m a
+ Reddit.Types.Reddit: ReceiveRoute :: Route -> (b -> a) -> RedditF m a
+ Reddit.Types.Reddit: RunRoute :: Route -> (b -> a) -> RedditF m a
+ Reddit.Types.Reddit: WithBaseURL :: Text -> RedditT m b -> (b -> a) -> RedditF m a
+ Reddit.Types.Reddit: data RedditF m a
+ Reddit.Types.Reddit: instance Functor (RedditF m)
+ Reddit.Types.Reddit: instance Monad m => Applicative (RedditT m)
+ Reddit.Types.Reddit: instance Monad m => Functor (RedditT m)
+ Reddit.Types.Reddit: instance Receivable LoginDetails
+ Reddit.Types.Reddit: receiveRoute :: (Receivable a, Monad m) => Route -> RedditT m a
+ Reddit.Types.Reddit: runRoute :: (FromJSON a, Monad m) => Route -> RedditT m a
+ Reddit.Types.Reddit: withBaseURL :: Monad m => Text -> RedditT m a -> RedditT m a
- Reddit.Actions.Captcha: needsCaptcha :: MonadIO m => RedditT m Bool
+ Reddit.Actions.Captcha: needsCaptcha :: Monad m => RedditT m Bool
- Reddit.Actions.Captcha: newCaptcha :: MonadIO m => RedditT m CaptchaID
+ Reddit.Actions.Captcha: newCaptcha :: Monad m => RedditT m CaptchaID
- Reddit.Actions.Comment: deleteComment :: MonadIO m => CommentID -> RedditT m ()
+ Reddit.Actions.Comment: deleteComment :: Monad m => CommentID -> RedditT m ()
- Reddit.Actions.Comment: editComment :: MonadIO m => CommentID -> Text -> RedditT m Comment
+ Reddit.Actions.Comment: editComment :: Monad m => CommentID -> Text -> RedditT m Comment
- Reddit.Actions.Comment: getCommentInfo :: MonadIO m => CommentID -> RedditT m Comment
+ Reddit.Actions.Comment: getCommentInfo :: Monad m => CommentID -> RedditT m Comment
- Reddit.Actions.Comment: getCommentsInfo :: MonadIO m => [CommentID] -> RedditT m CommentListing
+ Reddit.Actions.Comment: getCommentsInfo :: Monad m => [CommentID] -> RedditT m CommentListing
- Reddit.Actions.Comment: getMoreChildren :: MonadIO m => PostID -> [CommentID] -> RedditT m [CommentReference]
+ Reddit.Actions.Comment: getMoreChildren :: Monad m => PostID -> [CommentID] -> RedditT m [CommentReference]
- Reddit.Actions.Comment: getNewComments :: MonadIO m => Maybe SubredditName -> RedditT m CommentListing
+ Reddit.Actions.Comment: getNewComments :: Monad m => Maybe SubredditName -> RedditT m CommentListing
- Reddit.Actions.Comment: getNewComments' :: MonadIO m => Options CommentID -> Maybe SubredditName -> RedditT m CommentListing
+ Reddit.Actions.Comment: getNewComments' :: Monad m => Options CommentID -> Maybe SubredditName -> RedditT m CommentListing
- Reddit.Actions.Comment: removeComment :: MonadIO m => CommentID -> RedditT m ()
+ Reddit.Actions.Comment: removeComment :: Monad m => CommentID -> RedditT m ()
- Reddit.Actions.Flair: addLinkFlair :: MonadIO m => SubredditName -> Text -> Text -> Bool -> RedditT m ()
+ Reddit.Actions.Flair: addLinkFlair :: Monad m => SubredditName -> Text -> Text -> Bool -> RedditT m ()
- Reddit.Actions.Flair: flairCSV :: MonadIO m => SubredditName -> [(Username, Text, Text)] -> RedditT m Value
+ Reddit.Actions.Flair: flairCSV :: Monad m => SubredditName -> [(Username, Text, Text)] -> RedditT m Value
- Reddit.Actions.Flair: getFlairList :: MonadIO m => SubredditName -> RedditT m FlairListing
+ Reddit.Actions.Flair: getFlairList :: Monad m => SubredditName -> RedditT m FlairListing
- Reddit.Actions.Flair: getFlairList' :: MonadIO m => Options UserID -> SubredditName -> RedditT m FlairListing
+ Reddit.Actions.Flair: getFlairList' :: Monad m => Options UserID -> SubredditName -> RedditT m FlairListing
- Reddit.Actions.Message: getInbox :: MonadIO m => RedditT m (Listing MessageKind Message)
+ Reddit.Actions.Message: getInbox :: Monad m => RedditT m (Listing MessageKind Message)
- Reddit.Actions.Message: getInbox' :: MonadIO m => Bool -> Options MessageKind -> RedditT m (Listing MessageKind Message)
+ Reddit.Actions.Message: getInbox' :: Monad m => Bool -> Options MessageKind -> RedditT m (Listing MessageKind Message)
- Reddit.Actions.Message: getUnread :: MonadIO m => RedditT m (Listing MessageKind Message)
+ Reddit.Actions.Message: getUnread :: Monad m => RedditT m (Listing MessageKind Message)
- Reddit.Actions.Message: getUnread' :: MonadIO m => Bool -> Options MessageKind -> RedditT m (Listing MessageKind Message)
+ Reddit.Actions.Message: getUnread' :: Monad m => Bool -> Options MessageKind -> RedditT m (Listing MessageKind Message)
- Reddit.Actions.Message: markRead :: (ToQuery a, Thing a, MonadIO m) => a -> RedditT m ()
+ Reddit.Actions.Message: markRead :: (ToQuery a, Thing a, Monad m) => a -> RedditT m ()
- Reddit.Actions.Message: sendMessage :: MonadIO m => Username -> Text -> Text -> RedditT m ()
+ Reddit.Actions.Message: sendMessage :: Monad m => Username -> Text -> Text -> RedditT m ()
- Reddit.Actions.Message: sendMessageWithCaptcha :: MonadIO m => Username -> Text -> Text -> CaptchaID -> Text -> RedditT m ()
+ Reddit.Actions.Message: sendMessageWithCaptcha :: Monad m => Username -> Text -> Text -> CaptchaID -> Text -> RedditT m ()
- Reddit.Actions.Moderation: bans :: Options BanID -> SubredditName -> Reddit (Listing BanID Ban)
+ Reddit.Actions.Moderation: bans :: Monad m => Options BanID -> SubredditName -> RedditT m (Listing BanID Ban)
- Reddit.Actions.Moderation: lookupBan :: Username -> SubredditName -> Reddit (Maybe Ban)
+ Reddit.Actions.Moderation: lookupBan :: Monad m => Username -> SubredditName -> RedditT m (Maybe Ban)
- Reddit.Actions.Post: deletePost :: MonadIO m => PostID -> RedditT m ()
+ Reddit.Actions.Post: deletePost :: Monad m => PostID -> RedditT m ()
- Reddit.Actions.Post: editPost :: MonadIO m => PostID -> Text -> RedditT m ()
+ Reddit.Actions.Post: editPost :: Monad m => PostID -> Text -> RedditT m ()
- Reddit.Actions.Post: getComments :: MonadIO m => PostID -> RedditT m [CommentReference]
+ Reddit.Actions.Post: getComments :: Monad m => PostID -> RedditT m [CommentReference]
- Reddit.Actions.Post: getPostComments :: MonadIO m => PostID -> RedditT m PostComments
+ Reddit.Actions.Post: getPostComments :: Monad m => PostID -> RedditT m PostComments
- Reddit.Actions.Post: getPostInfo :: MonadIO m => PostID -> RedditT m Post
+ Reddit.Actions.Post: getPostInfo :: Monad m => PostID -> RedditT m Post
- Reddit.Actions.Post: getPostSubComments :: MonadIO m => PostID -> CommentID -> RedditT m PostComments
+ Reddit.Actions.Post: getPostSubComments :: Monad m => PostID -> CommentID -> RedditT m PostComments
- Reddit.Actions.Post: getPosts :: MonadIO m => RedditT m PostListing
+ Reddit.Actions.Post: getPosts :: Monad m => RedditT m PostListing
- Reddit.Actions.Post: getPosts' :: MonadIO m => Options PostID -> ListingType -> Maybe SubredditName -> RedditT m PostListing
+ Reddit.Actions.Post: getPosts' :: Monad m => Options PostID -> ListingType -> Maybe SubredditName -> RedditT m PostListing
- Reddit.Actions.Post: getPostsInfo :: MonadIO m => [PostID] -> RedditT m PostListing
+ Reddit.Actions.Post: getPostsInfo :: Monad m => [PostID] -> RedditT m PostListing
- Reddit.Actions.Post: markPostSpam :: MonadIO m => PostID -> RedditT m ()
+ Reddit.Actions.Post: markPostSpam :: Monad m => PostID -> RedditT m ()
- Reddit.Actions.Post: removePost :: MonadIO m => PostID -> RedditT m ()
+ Reddit.Actions.Post: removePost :: Monad m => PostID -> RedditT m ()
- Reddit.Actions.Post: savePost :: MonadIO m => PostID -> RedditT m ()
+ Reddit.Actions.Post: savePost :: Monad m => PostID -> RedditT m ()
- Reddit.Actions.Post: setContestMode :: MonadIO m => Bool -> PostID -> RedditT m ()
+ Reddit.Actions.Post: setContestMode :: Monad m => Bool -> PostID -> RedditT m ()
- Reddit.Actions.Post: setInboxReplies :: MonadIO m => Bool -> PostID -> RedditT m ()
+ Reddit.Actions.Post: setInboxReplies :: Monad m => Bool -> PostID -> RedditT m ()
- Reddit.Actions.Post: setPostFlair :: MonadIO m => SubredditName -> PostID -> Text -> Text -> RedditT m ()
+ Reddit.Actions.Post: setPostFlair :: Monad m => SubredditName -> PostID -> Text -> Text -> RedditT m ()
- Reddit.Actions.Post: stickyPost :: MonadIO m => PostID -> Maybe Integer -> RedditT m ()
+ Reddit.Actions.Post: stickyPost :: Monad m => PostID -> Maybe Integer -> RedditT m ()
- Reddit.Actions.Post: submitLink :: MonadIO m => SubredditName -> Text -> Text -> RedditT m PostID
+ Reddit.Actions.Post: submitLink :: Monad m => SubredditName -> Text -> Text -> RedditT m PostID
- Reddit.Actions.Post: submitLinkWithCaptcha :: MonadIO m => SubredditName -> Text -> Text -> CaptchaID -> Text -> RedditT m PostID
+ Reddit.Actions.Post: submitLinkWithCaptcha :: Monad m => SubredditName -> Text -> Text -> CaptchaID -> Text -> RedditT m PostID
- Reddit.Actions.Post: submitSelfPost :: MonadIO m => SubredditName -> Text -> Text -> RedditT m PostID
+ Reddit.Actions.Post: submitSelfPost :: Monad m => SubredditName -> Text -> Text -> RedditT m PostID
- Reddit.Actions.Post: submitSelfPostWithCaptcha :: MonadIO m => SubredditName -> Text -> Text -> CaptchaID -> Text -> RedditT m PostID
+ Reddit.Actions.Post: submitSelfPostWithCaptcha :: Monad m => SubredditName -> Text -> Text -> CaptchaID -> Text -> RedditT m PostID
- Reddit.Actions.Post: unsavePost :: MonadIO m => PostID -> RedditT m ()
+ Reddit.Actions.Post: unsavePost :: Monad m => PostID -> RedditT m ()
- Reddit.Actions.Post: unstickyPost :: MonadIO m => PostID -> Maybe Integer -> RedditT m ()
+ Reddit.Actions.Post: unstickyPost :: Monad m => PostID -> Maybe Integer -> RedditT m ()
- Reddit.Actions.Search: cloudSearch :: Maybe SubredditName -> Options PostID -> Order -> Text -> Reddit PostListing
+ Reddit.Actions.Search: cloudSearch :: Monad m => Maybe SubredditName -> Options PostID -> Order -> Text -> RedditT m PostListing
- Reddit.Actions.Search: luceneSearch :: Maybe SubredditName -> Options PostID -> Order -> Text -> Reddit PostListing
+ Reddit.Actions.Search: luceneSearch :: Monad m => Maybe SubredditName -> Options PostID -> Order -> Text -> RedditT m PostListing
- Reddit.Actions.Search: search :: Maybe SubredditName -> Options PostID -> Order -> Text -> Reddit PostListing
+ Reddit.Actions.Search: search :: Monad m => Maybe SubredditName -> Options PostID -> Order -> Text -> RedditT m PostListing
- Reddit.Actions.Subreddit: getSubredditInfo :: MonadIO m => SubredditName -> RedditT m Subreddit
+ Reddit.Actions.Subreddit: getSubredditInfo :: Monad m => SubredditName -> RedditT m Subreddit
- Reddit.Actions.Subreddit: getSubredditSettings :: MonadIO m => SubredditName -> RedditT m SubredditSettings
+ Reddit.Actions.Subreddit: getSubredditSettings :: Monad m => SubredditName -> RedditT m SubredditSettings
- Reddit.Actions.Subreddit: setSubredditSettings :: MonadIO m => SubredditID -> SubredditSettings -> RedditT m ()
+ Reddit.Actions.Subreddit: setSubredditSettings :: Monad m => SubredditID -> SubredditSettings -> RedditT m ()
- Reddit.Actions.Thing: delete :: (MonadIO m, Thing a) => a -> RedditT m ()
+ Reddit.Actions.Thing: delete :: (Monad m, Thing a) => a -> RedditT m ()
- Reddit.Actions.Thing: reply :: (MonadIO m, Thing a) => a -> Text -> RedditT m CommentID
+ Reddit.Actions.Thing: reply :: (Monad m, Thing a) => a -> Text -> RedditT m CommentID
- Reddit.Actions.Thing: report :: (MonadIO m, Thing a) => a -> Text -> RedditT m ()
+ Reddit.Actions.Thing: report :: (Monad m, Thing a) => a -> Text -> RedditT m ()
- Reddit.Actions.User: aboutMe :: MonadIO m => RedditT m User
+ Reddit.Actions.User: aboutMe :: Monad m => RedditT m User
- Reddit.Actions.User: getBlockedUsers :: MonadIO m => RedditT m [Relationship]
+ Reddit.Actions.User: getBlockedUsers :: Monad m => RedditT m [Relationship]
- Reddit.Actions.User: getFriends :: MonadIO m => RedditT m [Relationship]
+ Reddit.Actions.User: getFriends :: Monad m => RedditT m [Relationship]
- Reddit.Actions.User: getUserComments :: MonadIO m => Username -> RedditT m CommentListing
+ Reddit.Actions.User: getUserComments :: Monad m => Username -> RedditT m CommentListing
- Reddit.Actions.User: getUserComments' :: MonadIO m => Options CommentID -> Username -> RedditT m CommentListing
+ Reddit.Actions.User: getUserComments' :: Monad m => Options CommentID -> Username -> RedditT m CommentListing
- Reddit.Actions.User: getUserInfo :: MonadIO m => Username -> RedditT m User
+ Reddit.Actions.User: getUserInfo :: Monad m => Username -> RedditT m User
- Reddit.Actions.User: isUsernameAvailable :: MonadIO m => Username -> RedditT m Bool
+ Reddit.Actions.User: isUsernameAvailable :: Monad m => Username -> RedditT m Bool
- Reddit.Actions.User: lookupUserFlair :: MonadIO m => SubredditName -> Username -> RedditT m Flair
+ Reddit.Actions.User: lookupUserFlair :: Monad m => SubredditName -> Username -> RedditT m Flair
- Reddit.Actions.User: setUserFlair :: MonadIO m => SubredditName -> Username -> Text -> Text -> RedditT m ()
+ Reddit.Actions.User: setUserFlair :: Monad m => SubredditName -> Username -> Text -> Text -> RedditT m ()
- Reddit.Actions.Voting: downvoteComment :: MonadIO m => CommentID -> RedditT m ()
+ Reddit.Actions.Voting: downvoteComment :: Monad m => CommentID -> RedditT m ()
- Reddit.Actions.Voting: downvotePost :: MonadIO m => PostID -> RedditT m ()
+ Reddit.Actions.Voting: downvotePost :: Monad m => PostID -> RedditT m ()
- Reddit.Actions.Voting: unvoteComment :: MonadIO m => CommentID -> RedditT m ()
+ Reddit.Actions.Voting: unvoteComment :: Monad m => CommentID -> RedditT m ()
- Reddit.Actions.Voting: unvotePost :: MonadIO m => PostID -> RedditT m ()
+ Reddit.Actions.Voting: unvotePost :: Monad m => PostID -> RedditT m ()
- Reddit.Actions.Voting: upvoteComment :: MonadIO m => CommentID -> RedditT m ()
+ Reddit.Actions.Voting: upvoteComment :: Monad m => CommentID -> RedditT m ()
- Reddit.Actions.Voting: upvotePost :: MonadIO m => PostID -> RedditT m ()
+ Reddit.Actions.Voting: upvotePost :: Monad m => PostID -> RedditT m ()
- Reddit.Actions.Wiki: editWikiPage :: MonadIO m => SubredditName -> Text -> Text -> Text -> RedditT m ()
+ Reddit.Actions.Wiki: editWikiPage :: Monad m => SubredditName -> Text -> Text -> Text -> RedditT m ()
- Reddit.Actions.Wiki: getWikiPage :: MonadIO m => SubredditName -> Text -> RedditT m WikiPage
+ Reddit.Actions.Wiki: getWikiPage :: Monad m => SubredditName -> Text -> RedditT m WikiPage
- Reddit.Login: login :: MonadIO m => Text -> Text -> RedditT m LoginDetails
+ Reddit.Login: login :: Monad m => Text -> Text -> RedditT m LoginDetails
- Reddit.Types.Reddit: RedditT :: APIT (TVar RateLimits) RedditError m a -> RedditT m a
+ Reddit.Types.Reddit: RedditT :: (FreeT (RedditF m) m a) -> RedditT m a
- Reddit.Types.Reddit: nest :: MonadIO m => RedditT m a -> RedditT m (Either (APIError RedditError) a)
+ Reddit.Types.Reddit: nest :: Monad m => RedditT m a -> RedditT m (Either (APIError RedditError) a)

Files

README.md view
@@ -1,4 +1,4 @@-reddit for haskell [![Build Status](https://travis-ci.org/intolerable/reddit.svg?branch=v0.1)](https://travis-ci.org/intolerable/reddit)+reddit for haskell [![Build Status](https://travis-ci.org/intolerable/reddit.svg?branch=master)](https://travis-ci.org/intolerable/reddit) ---  A Haskell library for interacting with the [Reddit API](http://reddit.com/api).@@ -8,6 +8,7 @@ Let's get all the posts from the frontpage of Reddit and write a summary of each of them to the console:  ```haskell+{-# LANGUAGE OverloadedStrings #-} import Reddit import Reddit.Types.Post @@ -27,9 +28,10 @@ tshow = Text.pack . show ``` -Let's check to see which of a group of has the highest link karma:+Let's check to see which of a group of users has the highest link karma:  ```haskell+{-# LANGUAGE OverloadedStrings #-} import Reddit import Reddit.Types.User @@ -39,8 +41,8 @@ usersToCheck = ["nikita-volkov", "simonmar", "bos", "roche"]  main = runRedditAnon $ do-    infos <- mapM (getUserInfo . Username) usersToCheck-    return $ maximumBy (comparing linkKarma) infos+  infos <- mapM (getUserInfo . Username) usersToCheck+  return $ maximumBy (comparing linkKarma) infos ```  Testing
reddit.cabal view
@@ -1,14 +1,14 @@ name:                reddit-version:             0.1.1.0+version:             0.2.0.0 synopsis:            Library for interfacing with Reddit's API description:   A library for interfacing with Reddit's API in Haskell. Handles-  logins, rate-limiting and converted to and from JSON responses.+  logins, rate-limiting and converting to and from JSON responses.    Supports most user-facing Reddit API functions, as well as some   moderator endpoints. -  Check out the readme at https://github.com/intolerable/reddit.+  Check out the readme at <https://github.com/intolerable/reddit>.   Contributions are welcome.  license:             BSD2@@ -73,7 +73,6 @@     Reddit.Routes.Message     Reddit.Routes.Moderation     Reddit.Routes.Post-    Reddit.Routes.Run     Reddit.Routes.Search     Reddit.Routes.Subreddit     Reddit.Routes.Thing@@ -83,25 +82,29 @@     Reddit.Types.Empty     Reddit.Utilities   default-extensions:-    FlexibleInstances,+    FlexibleInstances+    GADTs+    GeneralizedNewtypeDeriving+    LambdaCase     OverloadedStrings   default-language: Haskell2010   hs-source-dirs: src/   build-depends:     base >= 4.6 && < 4.9,-    aeson,-    api-builder >= 0.9 && < 0.10,-    bytestring,-    data-default,-    http-conduit,-    http-types,-    network,-    stm,-    text,-    time,+    aeson >= 0.9 && < 0.10,+    api-builder >= 0.10 && < 0.12,+    bytestring == 0.10.*,+    data-default-class == 0.0.1,+    free >= 4 && < 5,+    http-client >= 0.4.11 && < 0.4.21,+    http-client-tls >= 0.2 && < 0.2.3,+    http-types == 0.8.*,+    network == 2.6.*,+    text == 1.*,+    time == 1.5.*,     transformers == 0.4.*,-    unordered-containers,-    vector+    unordered-containers == 0.2.5.*,+    vector >= 0.10 && < 0.12   ghc-options: -Wall  test-suite test@@ -113,9 +116,9 @@   build-depends:     base == 4.*,     Cabal >= 1.16.0,+    aeson,     api-builder,     bytestring,-    http-conduit,     reddit,     hspec,     text,@@ -136,7 +139,8 @@     Cabal >= 1.16.0,     api-builder,     bytestring,-    http-conduit,+    http-client,+    http-client-tls,     reddit,     hspec,     text,@@ -156,7 +160,6 @@     base == 4.*,     Cabal >= 1.16.0,     api-builder,-    http-conduit,     reddit,     hspec,     text,
src/Reddit.hs view
@@ -8,7 +8,10 @@   ( runReddit   , runRedditAnon   , runRedditWith+  , runResumeRedditWith+  , interpretIO   , RedditOptions(..)+  , defaultRedditOptions   , LoginMethod(..)   -- * Re-exports   , APIError(..)@@ -23,14 +26,18 @@ import Reddit.Types import Reddit.Types.Reddit hiding (info, should) -import Control.Concurrent.STM.TVar+import Control.Concurrent+import Control.Monad import Control.Monad.IO.Class+import Control.Monad.Trans.Free import Data.ByteString.Char8 (ByteString)-import Data.Default+import Data.Default.Class import Data.Text (Text) import Data.Text.Encoding (encodeUtf8)-import Network.API.Builder-import Network.HTTP.Conduit+import Network.API.Builder as API+import Network.HTTP.Client+import Network.HTTP.Client.TLS+import Network.HTTP.Types  -- | Options for how we should run the 'Reddit' action. --@@ -53,6 +60,10 @@ instance Default RedditOptions where   def = RedditOptions True Nothing Anonymous Nothing +-- | The default set of options+defaultRedditOptions :: RedditOptions+defaultRedditOptions = def+ -- | Should we log in to Reddit? If so, should we use a stored set of credentials --   or get a new fresh set? data LoginMethod = Anonymous -- ^ Don't login, instead use an anonymous account@@ -82,23 +93,78 @@ --   most things, but it's handy if you want to persist a connection over multiple 'Reddit' sessions or --   use a custom user agent string. runRedditWith :: MonadIO m => RedditOptions -> RedditT m a -> m (Either (APIError RedditError) a)-runRedditWith (RedditOptions rl man lm ua) (RedditT reddit) = do-  rli <- liftIO $ newTVarIO $ RateLimits rl Nothing+runRedditWith opts reddit = liftM dropResume $ runResumeRedditWith opts reddit++-- | Run a 'Reddit' or 'RedditT' action with custom settings. You probably won't need this function for+--   most things, but it's handy if you want to persist a connection over multiple 'Reddit' sessions or+--   use a custom user agent string.+runResumeRedditWith :: MonadIO m => RedditOptions -> RedditT m a -> m (Either (APIError RedditError, Maybe (RedditT m a)) a)+runResumeRedditWith (RedditOptions rl man lm _ua) reddit = do   manager <- case man of     Just m -> return m-    Nothing -> liftIO $ newManager conduitManagerSettings-  (res, _, _) <- runAPI builder manager rli $ do-    customizeRequest $ addHeader ua-    case lm of-      StoredDetails (LoginDetails (Modhash mh) cj) ->-        customizeRequest $ \r ->-          addHeader ua r { cookieJar = Just cj-                         , requestHeaders = ("X-Modhash", encodeUtf8 mh):requestHeaders r }-      Credentials user pass -> do-        LoginDetails (Modhash mh) cj <- unRedditT $ login user pass-        customizeRequest $ \r ->-          addHeader ua r { cookieJar = Just cj-                         , requestHeaders = ("X-Modhash", encodeUtf8 mh):requestHeaders r }-      Anonymous -> return ()-    reddit+    Nothing -> liftIO $ newManager tlsManagerSettings+  loginCreds <- case lm of+    Anonymous -> return $ Right Nothing+    StoredDetails ld -> return $ Right $ Just ld+    Credentials user pass -> liftM (fmap Just) $ interpretIO (RedditState loginBaseURL rl manager [] Nothing) $ login user pass+  case loginCreds of+    Left (err, _) -> return $ Left (err, Just reddit)+    Right lds ->+      interpretIO+        (RedditState mainBaseURL rl manager [("User-Agent", "reddit-haskell dev version")] lds) reddit++interpretIO :: MonadIO m => RedditState -> RedditT m a -> m (Either (APIError RedditError, Maybe (RedditT m a)) a)+interpretIO rstate (RedditT r) =+  runFreeT r >>= \case+    Pure x -> return $ Right x+    Free (WithBaseURL u x n) ->+      interpretIO (rstate { currentBaseURL = u }) x >>= \case+        Left (err, Just resume) ->+          return $ Left (err, Just $ resume >>= RedditT . n)+        Left (err, Nothing) -> return $ Left (err, Nothing)+        Right res -> interpretIO rstate $ RedditT $ n res+    Free (FailWith x) -> return $ Left (x, Nothing)+    Free (Nest x n) ->+      interpretIO rstate $ RedditT $ wrap $ NestResuming x (n . dropResume)+    Free (NestResuming x n) -> do+      res <- interpretIO rstate x+      interpretIO rstate $ RedditT $ n res+    Free (RunRoute route n) ->+      interpretIO rstate $ RedditT $ wrap $ ReceiveRoute route (n . unwrapJSON)+    Free (ReceiveRoute route n) ->+      handleReceive route rstate >>= \case+        Left err@(APIError (RateLimitError secs _)) ->+          if rateLimit rstate+            then do+              liftIO $ threadDelay $ fromInteger secs * 1000 * 1000+              interpretIO rstate $ RedditT $ wrap $ ReceiveRoute route n+            else return $ Left (err, Just $ RedditT $ wrap $ ReceiveRoute route n)+        Left err -> return $ Left (err, Just $ RedditT $ wrap $ ReceiveRoute route n)+        Right x -> interpretIO rstate $ RedditT $ n x++dropResume :: Either (APIError RedditError, Maybe (RedditT m a)) a -> Either (APIError RedditError) a+dropResume (Left (x, _)) = Left x+dropResume (Right x) = Right x++handleReceive :: (MonadIO m, Receivable a) => Route -> RedditState -> m (Either (APIError RedditError) a)+handleReceive r rstate = do+  (res, _, _) <- runAPI (builderFromState rstate) (connMgr rstate) () $+    API.runRoute r   return res++builderFromState :: RedditState -> Builder+builderFromState (RedditState burl _ _ hdrs (Just (LoginDetails (Modhash mh) cj))) =+  Builder "Reddit" burl addAPIType $+    \req -> addHeaders (("X-Modhash", encodeUtf8 mh):hdrs) req { cookieJar = Just cj }+builderFromState (RedditState burl _ _ hdrs Nothing) =+  Builder "Reddit" burl addAPIType (addHeaders hdrs)++addHeaders :: [Header] -> Request -> Request+addHeaders xs req = req { requestHeaders = requestHeaders req ++ xs }++data RedditState =+  RedditState { currentBaseURL :: Text+              , rateLimit :: Bool+              , connMgr :: Manager+              , _extraHeaders :: [Header]+              , _creds :: Maybe LoginDetails }
src/Reddit/Actions/Captcha.hs view
@@ -7,21 +7,18 @@   ( needsCaptcha   , newCaptcha ) where -import Reddit.Routes.Run import Reddit.Types.Captcha import Reddit.Types.Reddit import qualified Reddit.Routes.Captcha as Route -import Control.Monad.IO.Class- -- | Find out if the account currently logged in requires a captcha to be submitted for --   certain requests (like sending a private message or submitting a post).-needsCaptcha :: MonadIO m => RedditT m Bool+needsCaptcha :: Monad m => RedditT m Bool needsCaptcha = runRoute Route.needsCaptcha  -- | Returns the ID of a captcha to be completed (the image for which can be found at --   <http://reddit.com/captcha/$CAPTCHA_ID>)-newCaptcha :: MonadIO m => RedditT m CaptchaID+newCaptcha :: Monad m => RedditT m CaptchaID newCaptcha = do   POSTWrapped c <- runRoute Route.newCaptcha   return c
src/Reddit/Actions/Comment.hs view
@@ -10,7 +10,6 @@   , deleteComment   , removeComment ) where -import Reddit.Routes.Run import Reddit.Types.Comment import Reddit.Types.Empty import Reddit.Types.Error@@ -21,8 +20,7 @@ import Reddit.Types.Subreddit import qualified Reddit.Routes as Route -import Control.Monad.IO.Class-import Data.Default+import Data.Default.Class import Data.Text (Text) import Network.API.Builder (APIError(..)) @@ -30,18 +28,18 @@ --   This maps to <http://reddit.com/r/$SUBREDDIT/comments>, or <http://reddit.com/comments> --   if the subreddit is not specified. --   Note that none of the comments returned will have any child comments.-getNewComments :: MonadIO m => Maybe SubredditName -> RedditT m CommentListing+getNewComments :: Monad m => Maybe SubredditName -> RedditT m CommentListing getNewComments = getNewComments' def  -- | Get a 'CommentListing' for the most recent comments with the specified 'Options' and --   'SubredditName'. Note that none of the comments returned will have any child comments. --   If the 'Options' is 'def', then this function is identical to 'getNewComments'.-getNewComments' :: MonadIO m => Options CommentID -> Maybe SubredditName -> RedditT m CommentListing+getNewComments' :: Monad m => Options CommentID -> Maybe SubredditName -> RedditT m CommentListing getNewComments' opts r = runRoute $ Route.newComments opts r  -- | Expand children comments that weren't fetched on initial load. --   Equivalent to the web UI's "load more comments" button.-getMoreChildren :: MonadIO m+getMoreChildren :: Monad m                 => PostID -- ^ @PostID@ for the top-level                 -> [CommentID] -- ^ List of @CommentID@s to expand                 -> RedditT m [CommentReference]@@ -53,7 +51,7 @@   return $ rs ++ more  -- | Given a 'CommentID', 'getCommentInfo' will return the full details for that comment.-getCommentInfo :: MonadIO m => CommentID -> RedditT m Comment+getCommentInfo :: Monad m => CommentID -> RedditT m Comment getCommentInfo c = do   res <- getCommentsInfo [c]   case res of@@ -63,7 +61,7 @@ -- | Given a list of 'CommentID's, 'getCommentsInfo' will return another list containing --   the full details for all the comments. Note that Reddit's --   API imposes a limitation of 100 comments per request, so this function will fail immediately if given a list of more than 100 IDs.-getCommentsInfo :: MonadIO m => [CommentID] -> RedditT m CommentListing+getCommentsInfo :: Monad m => [CommentID] -> RedditT m CommentListing getCommentsInfo cs =   if null $ drop 100 cs     then do@@ -79,7 +77,7 @@     sameLength _ _ = False  -- | Edit a comment.-editComment :: MonadIO m+editComment :: Monad m             => CommentID -- ^ Comment to edit             -> Text -- ^ New comment text             -> RedditT m Comment@@ -89,10 +87,10 @@  -- | Deletes one of your own comments. Note that this is different from --   removing a comment as a moderator action.-deleteComment :: MonadIO m => CommentID -> RedditT m ()+deleteComment :: Monad m => CommentID -> RedditT m () deleteComment = nothing . runRoute . Route.delete  -- | Removes a comment (as a moderator action). Note that this is different --   from deleting a comment.-removeComment :: MonadIO m => CommentID -> RedditT m ()+removeComment :: Monad m => CommentID -> RedditT m () removeComment = nothing . runRoute . Route.removePost False
src/Reddit/Actions/Flair.hs view
@@ -6,7 +6,6 @@   , flairCSV ) where  import Reddit.Routes.Flair-import Reddit.Routes.Run import Reddit.Types.Empty import Reddit.Types.Flair import Reddit.Types.Options@@ -15,24 +14,23 @@ import Reddit.Types.User  import Control.Monad-import Control.Monad.IO.Class import Data.Aeson-import Data.Default+import Data.Default.Class import Data.Text (Text)  -- | Get the flair list for a subreddit. Requires moderator privileges on --   the subreddit.-getFlairList :: MonadIO m => SubredditName -> RedditT m FlairListing+getFlairList :: Monad m => SubredditName -> RedditT m FlairListing getFlairList = getFlairList' def  -- | Get the flair list for a subreddit (with 'Options'). Requires moderator --   privileges on the subreddit.-getFlairList' :: MonadIO m => Options UserID -> SubredditName -> RedditT m FlairListing+getFlairList' :: Monad m => Options UserID -> SubredditName -> RedditT m FlairListing getFlairList' opts r = liftM flistToListing $ runRoute (flairList opts r)  -- | Add link flair to the subreddit-wide template for a subreddit that you moderate. --   Requires moderator privileges on the subreddit.-addLinkFlair :: MonadIO m+addLinkFlair :: Monad m              => SubredditName -- ^ The subreddit whose template you want to modify              -> Text -- ^ The intended CSS class of the new link flair              -> Text -- ^ The intended text label of the new link flair@@ -41,6 +39,6 @@ addLinkFlair r c l e =   nothing $ runRoute $ addLinkFlairTemplate r c l e -flairCSV :: MonadIO m => SubredditName -> [(Username, Text, Text)] -> RedditT m Value+flairCSV :: Monad m => SubredditName -> [(Username, Text, Text)] -> RedditT m Value flairCSV r sets =   runRoute $ flairCSVRoute r sets
src/Reddit/Actions/Message.hs view
@@ -7,9 +7,9 @@   , getUnread'   , markRead   , sendMessage-  , sendMessageWithCaptcha ) where+  , sendMessageWithCaptcha+  , replyMessage ) where -import Reddit.Routes.Run import Reddit.Types.Captcha import Reddit.Types.Empty import Reddit.Types.Listing@@ -19,38 +19,38 @@ import Reddit.Types.Thing import Reddit.Types.User import qualified Reddit.Routes.Message as Route+import qualified Reddit.Routes.Thing as Route -import Control.Monad.IO.Class-import Data.Default+import Data.Default.Class import Data.Text (Text) import Network.API.Builder.Query  -- | Get the message inbox for the current user.-getInbox :: MonadIO m => RedditT m (Listing MessageKind Message)+getInbox :: Monad m => RedditT m (Listing MessageKind Message) getInbox = runRoute $ Route.inbox False def  -- | Don't use this for watching for new messages, Reddit's ordering on -- |   inbox messages is odd and not likely to work how you expect.-getInbox' :: MonadIO m => Bool -> Options MessageKind -> RedditT m (Listing MessageKind Message)+getInbox' :: Monad m => Bool -> Options MessageKind -> RedditT m (Listing MessageKind Message) getInbox' m o = runRoute $ Route.inbox m o  -- | Get any unread messages for the current user.-getUnread :: MonadIO m => RedditT m (Listing MessageKind Message)+getUnread :: Monad m => RedditT m (Listing MessageKind Message) getUnread = runRoute $ Route.unread False def  -- | Get unread messages for the current user, with options.-getUnread' :: MonadIO m+getUnread' :: Monad m            => Bool -- ^ Whether the orangered notifier should be marked "off"            -> Options MessageKind            -> RedditT m (Listing MessageKind Message) getUnread' m o = runRoute $ Route.unread m o  -- | Mark a message as read.-markRead :: (ToQuery a, Thing a, MonadIO m) => a -> RedditT m ()+markRead :: (ToQuery a, Thing a, Monad m) => a -> RedditT m () markRead = nothing . runRoute . Route.readMessage  -- | Send a private message to another user.-sendMessage :: MonadIO m+sendMessage :: Monad m             => Username -- ^ The username to send the message to             -> Text -- ^ The subject of the message being sent             -> Text -- ^ The body of the message being sent@@ -58,7 +58,7 @@ sendMessage u s b = nothing $ runRoute $ Route.sendMessage u s b  -- | Send a private message (with a captcha).-sendMessageWithCaptcha :: MonadIO m+sendMessageWithCaptcha :: Monad m                        => Username -- ^ The username to send the message to                        -> Text -- ^ The subject of the message being sent                        -> Text -- ^ The body of the message being sent@@ -66,3 +66,12 @@                        -> Text -- ^ The answer to the specified captcha                        -> RedditT m () sendMessageWithCaptcha u s b i c = nothing $ runRoute $ Route.sendMessage u s b `withCaptcha` (i, c)++-- | Reply to a message+replyMessage :: (Monad m, Thing a)+             => a -- ^ Thing to reply to+             -> Text -- ^ Response contents+             -> RedditT m MessageID+replyMessage t b = do+  POSTWrapped res <- runRoute $ Route.reply t b+  return res
src/Reddit/Actions/Moderation.hs view
@@ -1,7 +1,6 @@ module Reddit.Actions.Moderation where  import Reddit.Routes.Moderation-import Reddit.Routes.Run import Reddit.Types.Error import Reddit.Types.Listing import Reddit.Types.Moderation@@ -14,15 +13,17 @@  -- | Get a list of existing bans on a subreddit. --   User must be a moderator of the subreddit.-bans :: Options BanID -> SubredditName -> Reddit (Listing BanID Ban)+bans :: Monad m => Options BanID -> SubredditName -> RedditT m (Listing BanID Ban) bans opts r = runRoute $ bansListing opts r  -- | Check to see if a user is banned from a subreddit. Logged-in user must --   be a moderator of the subreddit-lookupBan :: Username -> SubredditName -> Reddit (Maybe Ban)+lookupBan :: Monad m => Username -> SubredditName -> RedditT m (Maybe Ban) lookupBan u r = do-  Listing _ _ bs <- runRoute $ banLookup u r :: Reddit (Listing BanID Ban)-  case bs of-    [b] -> return $ Just b-    [] -> return Nothing-    _-> failWith (APIError InvalidResponseError)+  res <- runRoute $ banLookup u r+  case res :: Listing BanID Ban of+    Listing _ _ bs ->+      case bs of+        [b] -> return $ Just b+        [] -> return Nothing+        _-> failWith (APIError InvalidResponseError)
src/Reddit/Actions/Post.hs view
@@ -26,7 +26,6 @@   , setContestMode ) where  import qualified Reddit.Routes as Route-import Reddit.Routes.Run import Reddit.Types import Reddit.Types.Captcha import Reddit.Types.Comment@@ -34,15 +33,14 @@ import Reddit.Types.Listing import Reddit.Types.Reddit -import Control.Monad.IO.Class-import Data.Default+import Data.Default.Class import Data.Text (Text) import Network.API.Builder.Error (APIError(..)) import qualified Data.Char as Char import qualified Data.Text as Text  -- | Given a 'PostID', 'getPostInfo' will return the full details for that post.-getPostInfo :: MonadIO m => PostID -> RedditT m Post+getPostInfo :: Monad m => PostID -> RedditT m Post getPostInfo p = do   res <- getPostsInfo [p]   case res of@@ -52,7 +50,7 @@ -- | Given a list of 'PostID's, 'getPostsInfo' will return another list containing --   the full details for all the posts. Note that Reddit's --   API imposes a limitation of 100 posts per request, so this function will fail immediately if given a list of more than 100 IDs.-getPostsInfo :: MonadIO m => [PostID] -> RedditT m PostListing+getPostsInfo :: Monad m => [PostID] -> RedditT m PostListing getPostsInfo ps =   -- we can only get 100 posts at a time or the api shits itself   if null $ drop 100 ps@@ -70,24 +68,24 @@  -- | Get a 'PostListing' for the 'Hot' posts on the site overall. --   This maps to <http://reddit.com>.-getPosts :: MonadIO m => RedditT m PostListing+getPosts :: Monad m => RedditT m PostListing getPosts = getPosts' def Hot Nothing  -- | Get a 'PostListing' for a specified listing.-getPosts' :: MonadIO m => Options PostID -> ListingType -> Maybe SubredditName -> RedditT m PostListing+getPosts' :: Monad m => Options PostID -> ListingType -> Maybe SubredditName -> RedditT m PostListing getPosts' o l r = runRoute $ Route.postsListing o r (Text.pack $ lower $ show l)   where lower = map Char.toLower  -- | Save a post.-savePost :: MonadIO m => PostID -> RedditT m ()+savePost :: Monad m => PostID -> RedditT m () savePost = nothing . runRoute . Route.savePost  -- | Remove a saved post from your "saved posts" list.-unsavePost :: MonadIO m => PostID -> RedditT m ()+unsavePost :: Monad m => PostID -> RedditT m () unsavePost = nothing . runRoute . Route.unsavePost  -- | Submit a new link to Reddit.-submitLink :: MonadIO m+submitLink :: Monad m            => SubredditName -- ^ The subreddit to which you're posting the link            -> Text -- ^ The title of the link post            -> Text -- ^ The link that you're posting@@ -97,7 +95,7 @@   return res  -- | Submit a new link to Reddit (answering a Captcha to prove we aren't a robot).-submitLinkWithCaptcha :: MonadIO m+submitLinkWithCaptcha :: Monad m                       => SubredditName -- ^ The subreddit to which you're posting the link                       -> Text -- ^ The title of the link post                       -> Text -- ^ The link that you're posting@@ -109,7 +107,7 @@   return res  -- | Submit a new selfpost to Reddit.-submitSelfPost :: MonadIO m+submitSelfPost :: Monad m                => SubredditName -- ^ The subreddit to which you're posting the selfpost                -> Text -- ^ The title of the selfpost                -> Text -- ^ The body of the selfpost@@ -119,7 +117,7 @@   return res  -- | Submit a new selfpost to Reddit (answering a Captcha to prove we aren't a robot).-submitSelfPostWithCaptcha :: MonadIO m+submitSelfPostWithCaptcha :: Monad m                           => SubredditName -- ^ The subreddit to which you're posting the selfpost                           -> Text -- ^ The title of the selfpost                           -> Text -- ^ The body of the selfpost@@ -132,12 +130,12 @@  -- | Deletes one of your own posts. Note that this is different from --   removing a post as a moderator action.-deletePost :: MonadIO m => PostID -> RedditT m ()+deletePost :: Monad m => PostID -> RedditT m () deletePost = nothing . runRoute . Route.delete  -- | Set the link flair for a post you've submitted (or any post on a subreddit --   that you moderate).-setPostFlair :: MonadIO m+setPostFlair :: Monad m              => SubredditName -- ^ The subreddit on which to set the flair              -> PostID -- ^ The post whose flair should be set              -> Text -- ^ The text label for the post's new flair@@ -146,49 +144,49 @@ setPostFlair r p text css = nothing $ runRoute $ Route.postFlair r p text css  -- | Edit the text of a self-post.-editPost :: MonadIO m => PostID -> Text -> RedditT m ()+editPost :: Monad m => PostID -> Text -> RedditT m () editPost thing text = nothing $ runRoute $ Route.edit thing text  -- | Get a post and all its comments.-getPostComments :: MonadIO m => PostID -> RedditT m PostComments+getPostComments :: Monad m => PostID -> RedditT m PostComments getPostComments p = runRoute $ Route.getComments p Nothing  -- | Get a post and a specific sub-tree of comments.-getPostSubComments :: MonadIO m => PostID -> CommentID -> RedditT m PostComments+getPostSubComments :: Monad m => PostID -> CommentID -> RedditT m PostComments getPostSubComments p c = runRoute $ Route.getComments p (Just c)  -- | Get the comments for a post. Ignore the actual post itself.-getComments :: MonadIO m => PostID -> RedditT m [CommentReference]+getComments :: Monad m => PostID -> RedditT m [CommentReference] getComments p = do   PostComments _ c <- getPostComments p   return c  -- | Set the state of inbox replies for the specified thread.-setInboxReplies :: MonadIO m => Bool -> PostID -> RedditT m ()+setInboxReplies :: Monad m => Bool -> PostID -> RedditT m () setInboxReplies enabled = nothing . runRoute . Route.sendReplies enabled  -- | Set the state of contest for the specified thread as a moderator action.-setContestMode :: MonadIO m => Bool -> PostID -> RedditT m ()+setContestMode :: Monad m => Bool -> PostID -> RedditT m () setContestMode enabled = nothing . runRoute . Route.setContestMode enabled  -- | Removes a post (as a moderator action). Note that this is different --   from deleting a post.-removePost :: MonadIO m => PostID -> RedditT m ()+removePost :: Monad m => PostID -> RedditT m () removePost = nothing . runRoute . Route.removePost False  -- | Mark a post as spam as a moderator action.-markPostSpam :: MonadIO m => PostID -> RedditT m ()+markPostSpam :: Monad m => PostID -> RedditT m () markPostSpam = nothing . runRoute . Route.removePost True  -- | Sticky a post on the subreddit on which it's posted.-stickyPost :: MonadIO m+stickyPost :: Monad m            => PostID -- ^ The post to be stickied            -> Maybe Integer -- ^ The position to which it should be stickied            -> RedditT m () stickyPost p n = nothing $ runRoute $ Route.stickyPost True p n  -- | Unsticky a post from the subreddit on which it's posted.-unstickyPost :: MonadIO m+unstickyPost :: Monad m            => PostID -- ^ The post to be unstickied            -> Maybe Integer -- ^ The position from which it should be unstickied            -> RedditT m ()
src/Reddit/Actions/Search.hs view
@@ -1,6 +1,5 @@ module Reddit.Actions.Search where -import Reddit.Routes.Run import Reddit.Routes.Search import Reddit.Types.Options import Reddit.Types.Post@@ -10,14 +9,14 @@  import Data.Text (Text) -search :: Maybe SubredditName -> Options PostID -> Search.Order -> Text -> Reddit PostListing+search :: Monad m => Maybe SubredditName -> Options PostID -> Search.Order -> Text -> RedditT m PostListing search sub opts order query =   runRoute $ searchRoute sub opts order "plain" query -luceneSearch :: Maybe SubredditName -> Options PostID -> Search.Order -> Text -> Reddit PostListing+luceneSearch :: Monad m => Maybe SubredditName -> Options PostID -> Search.Order -> Text -> RedditT m PostListing luceneSearch sub opts order query =   runRoute $ searchRoute sub opts order "lucene" query -cloudSearch :: Maybe SubredditName -> Options PostID -> Search.Order -> Text -> Reddit PostListing+cloudSearch :: Monad m => Maybe SubredditName -> Options PostID -> Search.Order -> Text -> RedditT m PostListing cloudSearch sub opts order query =   runRoute $ searchRoute sub opts order "cloudsearch" query
src/Reddit/Actions/Subreddit.hs view
@@ -4,24 +4,22 @@   , getSubredditSettings   , setSubredditSettings ) where -import Reddit.Routes.Run import Reddit.Types import Reddit.Types.Empty+import Reddit.Types.Reddit import Reddit.Types.Subreddit import Reddit.Types.SubredditSettings import qualified Reddit.Routes as Route -import Control.Monad.IO.Class- -- | Get the info for a specific subreddit. This info includes things like --   sidebar contents, description and ID.-getSubredditInfo :: MonadIO m => SubredditName -> RedditT m Subreddit+getSubredditInfo :: Monad m => SubredditName -> RedditT m Subreddit getSubredditInfo = runRoute . Route.aboutSubreddit  -- | Get the settings for a subreddit that you moderate.-getSubredditSettings :: MonadIO m => SubredditName -> RedditT m SubredditSettings+getSubredditSettings :: Monad m => SubredditName -> RedditT m SubredditSettings getSubredditSettings = runRoute . Route.subredditSettings  -- | Modify the settings for a subreddit that you moderate.-setSubredditSettings :: MonadIO m => SubredditID -> SubredditSettings -> RedditT m ()+setSubredditSettings :: Monad m => SubredditID -> SubredditSettings -> RedditT m () setSubredditSettings r s = nothing $ runRoute (Route.setSubredditSettings r s)
src/Reddit/Actions/Thing.hs view
@@ -5,17 +5,15 @@   , Reddit.Actions.Thing.delete   , Reddit.Actions.Thing.report ) where -import Reddit.Routes.Run import Reddit.Types import Reddit.Types.Empty import Reddit.Types.Reddit import qualified Reddit.Routes.Thing as Route -import Control.Monad.IO.Class import Data.Text (Text)  -- | Reply to a something (a post \/ comment \/ message)-reply :: (MonadIO m, Thing a)+reply :: (Monad m, Thing a)       => a -- ^ Thing to reply to       -> Text -- ^ Response contents       -> RedditT m CommentID@@ -26,13 +24,13 @@ -- | Delete something you created. Note that this is different to removing --   a post / comment as a moderator action. Deleting something you don't --   own won't error (but naturally won't delete anything either).-delete :: (MonadIO m, Thing a)+delete :: (Monad m, Thing a)        => a -- ^ Thing to delete        -> RedditT m () delete = nothing . runRoute . Route.delete  -- | Report something.-report :: (MonadIO m, Thing a)+report :: (Monad m, Thing a)        => a -- ^ Thing to report        -> Text -- ^ Reason for reporting        -> RedditT m ()
src/Reddit/Actions/User.hs view
@@ -5,66 +5,75 @@   , aboutMe   , getUserComments   , getUserComments'+  , getUserPosts+  , getUserPosts'   , isUsernameAvailable   , getBlockedUsers   , getFriends   , lookupUserFlair   , setUserFlair ) where -import Reddit.Routes.Run import Reddit.Types.Comment import Reddit.Types.Empty import Reddit.Types.Flair hiding (user) import Reddit.Types.Error import Reddit.Types.Listing import Reddit.Types.Options+import Reddit.Types.Post import Reddit.Types.Reddit import Reddit.Types.Subreddit import Reddit.Types.User import qualified Reddit.Routes.User as Route  import Control.Monad-import Control.Monad.IO.Class-import Data.Default+import Data.Default.Class import Data.Text (Text) import Network.API.Builder.Error import qualified Data.Text as Text  -- | Get the information Reddit exposes on user behind the specified username-getUserInfo :: MonadIO m => Username -> RedditT m User+getUserInfo :: Monad m => Username -> RedditT m User getUserInfo = runRoute . Route.aboutUser  -- | Get the listing of comments authored by the specified user.-getUserComments :: MonadIO m => Username -> RedditT m CommentListing+getUserComments :: Monad m => Username -> RedditT m CommentListing getUserComments = getUserComments' def  -- | Get the listing of comments authored by the specified user, with Options.-getUserComments' :: MonadIO m => Options CommentID -> Username -> RedditT m CommentListing+getUserComments' :: Monad m => Options CommentID -> Username -> RedditT m CommentListing getUserComments' opts user = runRoute $ Route.userComments opts user +-- | Get the listing of posts authored by the specified user.+getUserPosts :: Monad m => Username -> RedditT m PostListing+getUserPosts = getUserPosts' def++-- | Get the listing of posts authored by the specified user, with Options.+getUserPosts' :: Monad m => Options PostID -> Username -> RedditT m PostListing+getUserPosts' opts user = runRoute $ Route.userPosts opts user+ -- | Check whether the specified username is still available or has been taken.-isUsernameAvailable :: MonadIO m => Username -> RedditT m Bool+isUsernameAvailable :: Monad m => Username -> RedditT m Bool isUsernameAvailable = runRoute . Route.usernameAvailable  -- | Get information of the currently-logged-in user.-aboutMe :: MonadIO m => RedditT m User+aboutMe :: Monad m => RedditT m User aboutMe = runRoute Route.aboutMe  -- | Get users blocked by the currently-logged-in user.-getBlockedUsers :: MonadIO m => RedditT m [Relationship]+getBlockedUsers :: Monad m => RedditT m [Relationship] getBlockedUsers = do   UserList rs <- runRoute Route.blocked   return rs  -- | Get friends of the currently-logged-in user.-getFriends :: MonadIO m => RedditT m [Relationship]+getFriends :: Monad m => RedditT m [Relationship] getFriends = do   UserList rs <- runRoute Route.friends   return rs  -- | Check if a user has chosen (or been assign) user flair on a particular --   subreddit. Requires moderator privileges on the specified subreddit.-lookupUserFlair :: MonadIO m => SubredditName -> Username -> RedditT m Flair+lookupUserFlair :: Monad m => SubredditName -> Username -> RedditT m Flair lookupUserFlair r u = do   res <- liftM flistToListing $ runRoute (Route.lookupUserFlair r u)   case res of@@ -73,7 +82,7 @@  -- | Set a user's flair on the specified subreddit. Requires moderator --   privileges on the specified subreddit.-setUserFlair :: MonadIO m => SubredditName -> Username -> Text -> Text -> RedditT m ()+setUserFlair :: Monad m => SubredditName -> Username -> Text -> Text -> RedditT m () setUserFlair r u txt cls =   if Text.length txt > 64     then fail "Flair text too long!"
src/Reddit/Actions/Voting.hs view
@@ -14,37 +14,35 @@   , downvoteComment   , unvoteComment ) where -import Reddit.Routes.Run import Reddit.Routes.Vote (VoteDirection(..)) import Reddit.Types import Reddit.Types.Empty+import Reddit.Types.Reddit import qualified Reddit.Routes as Route -import Control.Monad.IO.Class--vote :: (MonadIO m, Thing a) => VoteDirection -> a -> RedditT m ()+vote :: (Monad m, Thing a) => VoteDirection -> a -> RedditT m () vote dir = nothing . runRoute . Route.vote dir  -- | Upvote a post.-upvotePost :: MonadIO m => PostID -> RedditT m ()+upvotePost :: Monad m => PostID -> RedditT m () upvotePost = vote UpVote  -- | Downvote a post.-downvotePost :: MonadIO m => PostID -> RedditT m ()+downvotePost :: Monad m => PostID -> RedditT m () downvotePost = vote DownVote  -- | Remove a vote from a post.-unvotePost :: MonadIO m => PostID -> RedditT m ()+unvotePost :: Monad m => PostID -> RedditT m () unvotePost = vote RemoveVote  -- | Upvote a comment.-upvoteComment :: MonadIO m => CommentID -> RedditT m ()+upvoteComment :: Monad m => CommentID -> RedditT m () upvoteComment = vote UpVote  -- | Downvote a comment.-downvoteComment :: MonadIO m => CommentID -> RedditT m ()+downvoteComment :: Monad m => CommentID -> RedditT m () downvoteComment = vote RemoveVote  -- | Remove a previously-cast vote from a comment.-unvoteComment :: MonadIO m => CommentID -> RedditT m ()+unvoteComment :: Monad m => CommentID -> RedditT m () unvoteComment = vote DownVote
src/Reddit/Actions/Wiki.hs view
@@ -3,24 +3,22 @@   ( getWikiPage   , editWikiPage ) where -import Reddit.Routes.Run import Reddit.Types.Empty import Reddit.Types.Reddit import Reddit.Types.Subreddit import Reddit.Types.Wiki import qualified Reddit.Routes as Route -import Control.Monad.IO.Class import Data.Text (Text)  -- | Get the specified wiki page on a particular subreddit. Requires --   permission to view the specified wiki page.-getWikiPage :: MonadIO m => SubredditName -> Text -> RedditT m WikiPage+getWikiPage :: Monad m => SubredditName -> Text -> RedditT m WikiPage getWikiPage sub page = runRoute $ Route.wikiPage sub page  -- | Edit the specified wiki page on a particular subreddit. Requires --   permission to edit the specified wiki page.-editWikiPage :: MonadIO m+editWikiPage :: Monad m              => SubredditName -- ^ Subreddit whose wiki to modify              -> Text -- ^ The name of the page you're editing              -> Text -- ^ The new markdown content of the page you're editing
src/Reddit/Login.hs view
@@ -1,17 +1,10 @@ module Reddit.Login   ( login ) where -import Reddit.Types.Error import Reddit.Types.Reddit -import Control.Concurrent (threadDelay)-import Control.Concurrent.STM.TVar-import Control.Monad.IO.Class (MonadIO, liftIO)-import Control.Monad.Trans.Except-import Control.Monad.Trans.State import Data.Text (Text)-import Network.API.Builder-import Network.HTTP.Conduit+import Network.API.Builder hiding (runRoute)  loginRoute :: Text -> Text -> Route loginRoute user pass = Route [ "api", "login" ]@@ -20,29 +13,12 @@                              , "passwd" =. pass ]                              "POST" -getLoginDetails :: MonadIO m => Text -> Text -> RedditT m LoginDetails-getLoginDetails user pass = do-  resp <- RedditT $ sendRoute () $ loginRoute user pass-  let cj = responseCookieJar resp-  case unwrapJSON `fmap` receive resp of-    Left x@(APIError (RateLimitError wait _)) -> do-      RateLimits limiting _ <- RedditT $ liftState get >>= liftIO . readTVarIO-      if limiting-        then do-          liftIO $ threadDelay $ (fromIntegral wait + 5) * 1000000-          getLoginDetails user pass-        else RedditT $ ExceptT $ return $ Left x-    Left x -> RedditT $ ExceptT $ return $ Left x-    Right modhash -> return $ LoginDetails modhash cj-+getLoginDetails :: Monad m => Text -> Text -> RedditT m LoginDetails+getLoginDetails user pass = receiveRoute $ loginRoute user pass  -- | Make a login request with the given username and password.-login :: MonadIO m+login :: Monad m       => Text -- ^ Username to login with       -> Text -- ^ Password to login with       -> RedditT m LoginDetails-login user pass = do-  RedditT $ baseURL loginBaseURL-  d <- getLoginDetails user pass-  RedditT $ baseURL mainBaseURL-  return d+login = getLoginDetails
− src/Reddit/Routes/Run.hs
@@ -1,81 +0,0 @@-module Reddit.Routes.Run-  ( runRoute ) where--import Reddit.Types.Error-import Reddit.Types.Reddit--import Control.Applicative-import Control.Concurrent (threadDelay)-import Control.Concurrent.STM-import Control.Monad (liftM, when)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Control.Monad.Trans.Except-import Control.Monad.Trans.State-import Data.Aeson (FromJSON(..))-import Data.ByteString.Lazy (ByteString)-import Data.Time.Clock-import Network.API.Builder.Routes (Route)-import Network.HTTP.Conduit-import Network.HTTP.Types-import Prelude-import qualified Network.API.Builder as API--runRoute :: (MonadIO m, FromJSON a) => Route -> RedditT m a-runRoute route = do-  RateLimits limiting _ <- RedditT $ API.liftState get >>= liftIO . readTVarIO-  if limiting-    then runRouteWithLimiting route-    else RedditT $ API.unwrapJSON `liftM` API.runRoute route--runRouteWithLimiting :: (MonadIO m, FromJSON a) => Route -> RedditT m a-runRouteWithLimiting route = do-  RateLimits _ rli <- RedditT $ API.liftState get >>= liftIO . readTVarIO-  case rli of-    Just r -> do-      when (needsReset r) $ waitForReset $ resetTime r-      requestAndUpdate-    Nothing -> requestAndUpdate-  where-    requestAndUpdate = do-      resp <- nest $ RedditT $ API.routeResponse route-      case resp of-        Left (API.APIError (RateLimitError resetIn _)) -> do-          updateFromZero resetIn-          runRouteWithLimiting route-        Left x -> RedditT $ ExceptT $ return $ Left x-        Right x -> do-          case API.unwrapJSON <$> API.receive x of-            Left (API.APIError (RateLimitError resetIn _)) -> do-              updateFromZero resetIn-              runRouteWithLimiting route-            Left y -> RedditT $ ExceptT $ return $ Left y-            Right y -> do-              updateRateLimitInfo $ responseHeaders x-              return y--updateRateLimitInfo :: MonadIO m => ResponseHeaders -> RedditT m ()-updateRateLimitInfo hs = do-  time <- liftIO getCurrentTime-  case headersToRateLimitInfo hs time of-    Just rli ->-      RedditT $ do-        r <- API.liftState get-        liftIO $ atomically $ modifyTVar r (\(RateLimits b _) -> RateLimits b $ Just rli)-    Nothing -> return ()--updateFromZero :: MonadIO m => Integer -> RedditT m ()-updateFromZero resetIn = do-  time <- liftIO getCurrentTime-  let resetAt = addUTCTime (fromInteger resetIn) time-  RedditT $ do-    r <- API.liftState get-    liftIO $ atomically $ modifyTVar r (\(RateLimits b _) -> RateLimits b $ Just $ RateLimitInfo 0 0 resetAt)--waitForReset :: MonadIO m => UTCTime -> RedditT m ()-waitForReset dt = do-  time <- liftIO getCurrentTime-  let wait = floor $ diffUTCTime dt time-  liftIO $ threadDelay $ (wait + 5) * 1000000 -- wait five extra seconds to account for timing differences--needsReset :: RateLimitInfo -> Bool-needsReset = (<= 0) . remaining
src/Reddit/Routes/User.hs view
@@ -2,6 +2,7 @@  import Reddit.Types.Comment (CommentID) import Reddit.Types.Options+import Reddit.Types.Post import Reddit.Types.Subreddit import Reddit.Types.User @@ -25,6 +26,15 @@         , "before" =. before opts         , "after" =. after opts ]         "GET"++userPosts :: Options PostID -> Username -> Route+userPosts opts (Username user) = +  Route [ "user", user, "submitted" ]+        [ "limit" =. limit opts +        , "before" =. before opts+        , "after" =. after opts ]+        "GET"+  usernameAvailable :: Username -> Route usernameAvailable user = Route [ "api", "username_available.json" ]
src/Reddit/Types/Message.hs view
@@ -3,6 +3,7 @@ import Reddit.Parser import Reddit.Types.Comment import Reddit.Types.Listing+import Reddit.Types.Reddit import Reddit.Types.Thing import Reddit.Types.User import Reddit.Utilities@@ -14,6 +15,7 @@ import Data.Text (Text) import Network.API.Builder.Query import Prelude+import qualified Data.Vector as Vector  data Message = Message { messageID :: MessageKind                        , new :: Bool@@ -57,7 +59,7 @@     _ -> False  data MessageID = MessageID Text-  deriving (Show, Read, Eq)+  deriving (Show, Read, Eq, Ord)  instance FromJSON MessageID where   parseJSON (String s) =@@ -70,12 +72,20 @@ instance ToQuery MessageID where   toQuery k m = toQuery k (fullName m) +instance FromJSON (POSTWrapped MessageID) where+  parseJSON (Object o) = do+    ms <- (o .: "json") >>= (.: "data") >>= (.: "things")+    case Vector.toList ms of+      [v] -> POSTWrapped <$> (v .: "data" >>= (.: "id"))+      _ -> mempty+  parseJSON _ = mempty+ messagePrefix :: Text messagePrefix = "t4"  data MessageKind = CommentMessage CommentID                  | PrivateMessage MessageID-  deriving (Show, Read, Eq)+  deriving (Show, Read, Eq, Ord)  instance FromJSON MessageKind where   parseJSON s =
src/Reddit/Types/Options.hs view
@@ -1,6 +1,6 @@ module Reddit.Types.Options where -import Data.Default+import Data.Default.Class  data PaginationOption a = Before a                         | After a
src/Reddit/Types/Reddit.hs view
@@ -1,8 +1,12 @@ module Reddit.Types.Reddit   ( Reddit   , RedditT(..)+  , RedditF(..)+  , runRoute+  , receiveRoute   , nest   , failWith+  , withBaseURL   , Modhash(..)   , LoginDetails(..)   , POSTWrapped(..)@@ -19,59 +23,62 @@ import Reddit.Types.Error  import Control.Applicative-import Control.Concurrent.STM.TVar import Control.Monad.IO.Class+import Control.Monad.Trans.Free import Control.Monad.Trans.Class-import Control.Monad.Trans.Except-import Control.Monad.Trans.Reader (ask)-import Control.Monad.Trans.State (get, put) import Data.Aeson+import Data.ByteString.Lazy (ByteString) import Data.Monoid import Data.Text (Text) import Data.Time.Clock-import Network.API.Builder-import Network.HTTP.Conduit hiding (path)+import Network.API.Builder hiding (runRoute)+import Network.HTTP.Client hiding (path) import Network.HTTP.Types import Prelude import Text.Read (readMaybe) import qualified Data.ByteString.Char8 as BS-import qualified Data.Text as Text  type Reddit a = RedditT IO a -newtype RedditT m a = RedditT { unRedditT :: APIT (TVar RateLimits) RedditError m a }+data RedditF m a where+  FailWith :: APIError RedditError -> RedditF m a+  Nest :: RedditT m b -> (Either (APIError RedditError) b -> a) -> RedditF m a+  NestResuming :: RedditT m b -> (Either (APIError RedditError, Maybe (RedditT m b)) b -> a) -> RedditF m a+  ReceiveRoute :: Receivable b => Route -> (b -> a) -> RedditF m a+  RunRoute :: FromJSON b => Route -> (b -> a) -> RedditF m a+  WithBaseURL :: Text -> RedditT m b -> (b -> a) -> RedditF m a -instance Functor m => Functor (RedditT m) where-  fmap f (RedditT a) = RedditT (fmap f a)+instance Functor (RedditF m) where+  fmap _ (FailWith x) = FailWith x+  fmap f (Nest a x) = Nest a (fmap f x)+  fmap f (NestResuming a x) = NestResuming a (fmap f x)+  fmap f (ReceiveRoute r x) = ReceiveRoute r (fmap f x)+  fmap f (RunRoute r x) = RunRoute r (fmap f x)+  fmap f (WithBaseURL u a x) = WithBaseURL u a (fmap f x) -instance (Functor m, Monad m) => Applicative (RedditT m) where-  pure a = RedditT (pure a)-  (RedditT f) <*> (RedditT a) = RedditT (f <*> a)+newtype RedditT m a = RedditT (FreeT (RedditF m) m a)+  deriving (Functor, Applicative, Monad) -instance Monad m => Monad (RedditT m) where-  return a = RedditT (return a)-  (RedditT a) >>= f = RedditT (a >>= unRedditT . f)-  fail = failWith . APIError . FailError . Text.pack+instance MonadTrans RedditT where+  lift = RedditT . lift  instance MonadIO m => MonadIO (RedditT m) where-  liftIO a = RedditT (liftIO a)+  liftIO = RedditT . liftIO -instance MonadTrans RedditT where-  lift = RedditT . lift . lift . lift . lift+runRoute :: (FromJSON a, Monad m) => Route -> RedditT m a+runRoute r = RedditT $ liftF $ RunRoute r id -nest :: MonadIO m => RedditT m a -> RedditT m (Either (APIError RedditError) a)-nest (RedditT a) = do-  b <- RedditT $ liftBuilder get-  rl <- RedditT $ liftState get-  m <- RedditT $ liftManager ask-  (res, b', rl') <- lift $ runAPI b m rl a-  RedditT $ do-    liftBuilder $ put b'-    liftState $ put rl'-  return res+receiveRoute :: (Receivable a, Monad m) => Route -> RedditT m a+receiveRoute r = RedditT $ liftF $ ReceiveRoute r id +nest :: Monad m => RedditT m a -> RedditT m (Either (APIError RedditError) a)+nest f = RedditT $ liftF $ Nest f id++withBaseURL :: Monad m => Text -> RedditT m a -> RedditT m a+withBaseURL u f = RedditT $ liftF $ WithBaseURL u f id+ failWith :: Monad m => APIError RedditError -> RedditT m a-failWith = RedditT . throwE+failWith = RedditT . liftF . FailWith  newtype Modhash = Modhash Text   deriving (Show, Read, Eq)@@ -83,6 +90,11 @@  data LoginDetails = LoginDetails Modhash CookieJar   deriving (Show, Eq)++instance Receivable LoginDetails where+  receive x = do+    (resp, mh) <- receive x+    return $ LoginDetails (unwrapJSON mh) (responseCookieJar (resp :: Response ByteString))  newtype POSTWrapped a = POSTWrapped a   deriving (Show, Read, Eq)
src/Reddit/Types/SubredditSettings.hs view
@@ -10,7 +10,7 @@  import Control.Applicative import Data.Aeson-import Data.Default+import Data.Default.Class import Data.Monoid hiding (Any(..)) import Data.Text (Text) import Network.API.Builder.Query