packages feed

heddit 0.1 → 0.2

raw patch · 22 files changed

+375/−130 lines, 22 filesdep ~containersdep ~generic-lensdep ~hashablePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: containers, generic-lens, hashable, http-client-tls, microlens, microlens-ghc, microlens-platform, mtl, scientific, split, unordered-containers

API changes (from Hackage documentation)

- Network.Reddit.Types.Account: data Username
- Network.Reddit.Types.Account: instance Data.Aeson.Types.FromJSON.FromJSON Network.Reddit.Types.Account.Username
- Network.Reddit.Types.Account: instance Data.Aeson.Types.ToJSON.ToJSON Network.Reddit.Types.Account.Username
- Network.Reddit.Types.Account: instance GHC.Classes.Eq Network.Reddit.Types.Account.Username
- Network.Reddit.Types.Account: instance GHC.Generics.Generic Network.Reddit.Types.Account.Username
- Network.Reddit.Types.Account: instance GHC.Show.Show Network.Reddit.Types.Account.Username
- Network.Reddit.Types.Account: instance Web.Internal.HttpApiData.ToHttpApiData Network.Reddit.Types.Account.Username
- Network.Reddit.Types.Account: mkUsername :: MonadThrow m => Text -> m Username
+ Network.Reddit: BannedUser :: Username -> Integer -> Bool -> BannedUser
+ Network.Reddit: UserIsBanned :: BannedUser -> APIException
+ Network.Reddit: data BannedUser
+ Network.Reddit: isUserDeleted :: Username -> Bool
+ Network.Reddit: pattern DeletedUser :: Username
+ Network.Reddit: withReadOnly :: MonadReddit m => m a -> m a
+ Network.Reddit.Me: distrustUser :: MonadReddit m => Username -> m ()
+ Network.Reddit.Me: getMyFriend :: MonadReddit m => Username -> m Friend
+ Network.Reddit.Me: setMySubredditSettings :: MonadReddit m => SubredditSettings -> m ()
+ Network.Reddit.Me: trustUser :: MonadReddit m => Username -> m ()
+ Network.Reddit.Moderation: FlairConfig :: Bool -> Bool -> Bool -> FlairPosition -> FlairPosition -> FlairConfig
+ Network.Reddit.Moderation: OnLeft :: FlairPosition
+ Network.Reddit.Moderation: OnRight :: FlairPosition
+ Network.Reddit.Moderation: configureSubredditFlair :: MonadReddit m => SubredditName -> FlairConfig -> m ()
+ Network.Reddit.Moderation: data FlairConfig
+ Network.Reddit.Moderation: data FlairPosition
+ Network.Reddit.Moderation: defaultFlairConfig :: FlairConfig
+ Network.Reddit.Types.Account: data UserModeratedList
+ Network.Reddit.Types.Account: instance Data.Aeson.Types.FromJSON.FromJSON Network.Reddit.Types.Account.UserModeratedList
+ Network.Reddit.Types.Account: instance GHC.Generics.Generic Network.Reddit.Types.Account.UserModeratedList
+ Network.Reddit.Types.Account: instance GHC.Show.Show Network.Reddit.Types.Account.UserModeratedList
+ Network.Reddit.Types.Flair: FlairConfig :: Bool -> Bool -> Bool -> FlairPosition -> FlairPosition -> FlairConfig
+ Network.Reddit.Types.Flair: OnLeft :: FlairPosition
+ Network.Reddit.Types.Flair: OnRight :: FlairPosition
+ Network.Reddit.Types.Flair: [$sel:enabled:FlairConfig] :: FlairConfig -> Bool
+ Network.Reddit.Types.Flair: [$sel:linkPosition:FlairConfig] :: FlairConfig -> FlairPosition
+ Network.Reddit.Types.Flair: [$sel:linkSelfAssign:FlairConfig] :: FlairConfig -> Bool
+ Network.Reddit.Types.Flair: [$sel:position:FlairConfig] :: FlairConfig -> FlairPosition
+ Network.Reddit.Types.Flair: [$sel:selfAssign:FlairConfig] :: FlairConfig -> Bool
+ Network.Reddit.Types.Flair: data FlairConfig
+ Network.Reddit.Types.Flair: data FlairPosition
+ Network.Reddit.Types.Flair: defaultFlairConfig :: FlairConfig
+ Network.Reddit.Types.Flair: instance GHC.Classes.Eq Network.Reddit.Types.Flair.FlairConfig
+ Network.Reddit.Types.Flair: instance GHC.Classes.Eq Network.Reddit.Types.Flair.FlairPosition
+ Network.Reddit.Types.Flair: instance GHC.Generics.Generic Network.Reddit.Types.Flair.FlairConfig
+ Network.Reddit.Types.Flair: instance GHC.Generics.Generic Network.Reddit.Types.Flair.FlairPosition
+ Network.Reddit.Types.Flair: instance GHC.Show.Show Network.Reddit.Types.Flair.FlairConfig
+ Network.Reddit.Types.Flair: instance GHC.Show.Show Network.Reddit.Types.Flair.FlairPosition
+ Network.Reddit.Types.Flair: instance Web.Internal.FormUrlEncoded.ToForm Network.Reddit.Types.Flair.FlairConfig
+ Network.Reddit.Types.Flair: instance Web.Internal.HttpApiData.ToHttpApiData Network.Reddit.Types.Flair.FlairPosition
+ Network.Reddit.Types.Internal: BannedUser :: Username -> Integer -> Bool -> BannedUser
+ Network.Reddit.Types.Internal: ModeratedListKind :: RedditKind
+ Network.Reddit.Types.Internal: UserIsBanned :: BannedUser -> APIException
+ Network.Reddit.Types.Internal: [$sel:isSuspended:BannedUser] :: BannedUser -> Bool
+ Network.Reddit.Types.Internal: [$sel:name:BannedUser] :: BannedUser -> Username
+ Network.Reddit.Types.Internal: [$sel:totalKarma:BannedUser] :: BannedUser -> Integer
+ Network.Reddit.Types.Internal: data BannedUser
+ Network.Reddit.Types.Internal: data Username
+ Network.Reddit.Types.Internal: instance Data.Aeson.Types.FromJSON.FromJSON Network.Reddit.Types.Internal.BannedUser
+ Network.Reddit.Types.Internal: instance Data.Aeson.Types.FromJSON.FromJSON Network.Reddit.Types.Internal.Username
+ Network.Reddit.Types.Internal: instance Data.Aeson.Types.ToJSON.ToJSON Network.Reddit.Types.Internal.Username
+ Network.Reddit.Types.Internal: instance GHC.Classes.Eq Network.Reddit.Types.Internal.BannedUser
+ Network.Reddit.Types.Internal: instance GHC.Classes.Eq Network.Reddit.Types.Internal.Username
+ Network.Reddit.Types.Internal: instance GHC.Generics.Generic Network.Reddit.Types.Internal.BannedUser
+ Network.Reddit.Types.Internal: instance GHC.Generics.Generic Network.Reddit.Types.Internal.Username
+ Network.Reddit.Types.Internal: instance GHC.Show.Show Network.Reddit.Types.Internal.BannedUser
+ Network.Reddit.Types.Internal: instance GHC.Show.Show Network.Reddit.Types.Internal.Username
+ Network.Reddit.Types.Internal: instance Web.Internal.HttpApiData.ToHttpApiData Network.Reddit.Types.Internal.Username
+ Network.Reddit.Types.Internal: isUserDeleted :: Username -> Bool
+ Network.Reddit.Types.Internal: mkUsername :: MonadThrow m => Text -> m Username
+ Network.Reddit.Types.Internal: pattern DeletedUser :: Username
+ Network.Reddit.Types.Internal: usernameToDisplayName :: Username -> Text
+ Network.Reddit.User: getUserModerated :: MonadReddit m => Username -> m (Seq Subreddit)
+ Network.Reddit.User: getUserSubreddit :: MonadReddit m => Username -> m Subreddit
- Network.Reddit.Live: LiveUpdate :: LiveUpdateID -> Maybe Username -> Body -> Body -> Bool -> Seq LiveUpdateEmbed -> LiveUpdate
+ Network.Reddit.Live: LiveUpdate :: LiveUpdateID -> Username -> Body -> Body -> Bool -> Seq LiveUpdateEmbed -> LiveUpdate
- Network.Reddit.Types.Live: LiveUpdate :: LiveUpdateID -> Maybe Username -> Body -> Body -> Bool -> Seq LiveUpdateEmbed -> LiveUpdate
+ Network.Reddit.Types.Live: LiveUpdate :: LiveUpdateID -> Username -> Body -> Body -> Bool -> Seq LiveUpdateEmbed -> LiveUpdate
- Network.Reddit.Types.Live: [$sel:author:LiveUpdate] :: LiveUpdate -> Maybe Username
+ Network.Reddit.Types.Live: [$sel:author:LiveUpdate] :: LiveUpdate -> Username

Files

CHANGELOG.org view
@@ -1,5 +1,15 @@ #+TITLE: CHANGELOG +* 0.2++ Add ~withReadOnly~ to run actions in a non-user context++ Added to ~Network.Reddit.Me~ : ~getMyFriend~ to get a single ~Friend~, ~trustUser~ and ~distrustUser~, and ~setMySubredditSettings~++ Added to ~Network.Reddit.User~ : ~getUserSubreddit~ and ~getUserModerated~++ ~getMe~ will now throw an exception when called in a non-user context++ When looking up a banned/shadow-banned user with ~getUser~, a ~UserIsBanned~ exception will now be thrown. This contains a ~BannedUser~ record with some details about the user in question++ Add the pattern synonym ~DeletedUser~ and boolean test ~isUserDeleted~ to conveniently match "[deleted]" usernames++ Add ~configureSubredditFlair~++ The ~author~ field of a ~LiveUpdate~ is now just a ~Username~ instead of ~Maybe Username~. If the JSON value is ~null~, ~DeletedUser~ is used as a default+ * 0.1 + Add actions for awarding/gilding items and gilding users + New module ~Network.Reddit.Types.Award~ exports ~Award~, ~AwardID~, ~Awarding~, and ~AwardingsSummary~ types; ~Trophy~ moved to this module as well
README.org view
@@ -100,6 +100,7 @@ - The Reddit API seems overwhemlingly geared towards dynamic representations of data. This can cause obvious issues when decoding its JSON responses in Haskell. If an exception is thrown when trying to hit an endpoint, please let me know by opening an issue or PR! - ~heddit~ only supports authenticating via OAuth (see [[file:./doc/auth.org][Authenticating]]) and cannot access Reddit's old JSON API. This means that it is not possible to create a truly anonymous client. Instead, you must register all applications, even those without a user context. - At the moment, it doesn't build on GHC < 8.8.3, so if you are working with an older GHC version, ~heddit~ will not be suitable.+- Currently, there is no capability to provide captcha IDs to solve captchas.  * Examples Here are some executable examples using different features from ~heddit~ that hopefully illustrate how to use the library and accomplish different tasks. At the moment, the available example programs are:
heddit.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               heddit-version:            0.1+version:            0.2 synopsis:           Reddit API bindings description:        See the README at https://gitlab.com/ngua/heddit license:            BSD-3-Clause@@ -86,31 +86,32 @@   build-depends:     , aeson                 ^>=1.5     , aeson-casing          ^>=0.2-    , base                  >=4.11    && <5+    , base                  >=4.11 && <5     , bytestring            >=0.9-    , case-insensitive      >=1.0     && <1.3+    , case-insensitive      >=1.0  && <1.3     , conduit               ^>=1.3     , conduit-extra         ^>=1.3     , config-ini            ^>=0.2-    , containers            ^>=0.6+    , containers            >=0.6     , exceptions            ^>=0.10     , filepath              ^>=1.4-    , generic-lens          >=1.1     && <2.2-    , hashable              >=1.0.1.1 && <1.4+    , generic-lens          >=1.1  && <2.3+    , hashable              ^>=1.3     , http-api-data         ^>=0.4-    , http-client           >=0.5     && <0.8-    , http-client-tls       ^>=0.3.5+    , http-client           >=0.5  && <0.8+    , http-client-tls       ^>=0.3     , http-conduit          ^>=2.3     , http-types            ^>=0.12-    , microlens             ^>=0.4.10-    , mtl                   ^>=2.2+    , microlens             ^>=0.4+    , microlens-ghc         ^>=0.4+    , mtl                   >=2.2     , random                >=1.1-    , scientific            ^>=0.3.6-    , split                 ^>=0.2.3+    , scientific            ^>=0.3+    , split                 ^>=0.2     , text                  ^>=1.2-    , time                  >=1.9     && <1.13+    , time                  >=1.9  && <1.13     , unliftio              ^>=0.2-    , unordered-containers  ^>=0.2.10+    , unordered-containers  ^>=0.2     , uri-bytestring        ^>=0.3  executable refresh-tokens@@ -122,12 +123,12 @@    build-depends:     , aeson               ^>=1.5-    , base                >=4.13  && <5+    , base                >=4.13 && <5     , bytestring          >=0.9-    , generic-lens        >=1.1   && <2.2+    , generic-lens        >=1.1  && <2.3     , heddit     , http-api-data       ^>=0.4-    , microlens-platform  ^>=0.4.2+    , microlens-platform  ^>=0.4     , network             ^>=3.1     , random              >=1.1     , text                ^>=1.2@@ -144,10 +145,10 @@     buildable: False    build-depends:-    , base                >=4.13  && <5-    , generic-lens        >=1.1   && <2.2+    , base                >=4.13 && <5+    , generic-lens        >=1.1  && <2.3     , heddit-    , microlens-platform  ^>=0.4.2+    , microlens-platform  ^>=0.4     , text                ^>=1.2    main-is:          Mods.hs@@ -162,11 +163,11 @@     buildable: False    build-depends:-    , base                >=4.13  && <5+    , base                >=4.13 && <5     , conduit             ^>=1.3-    , generic-lens        >=1.1   && <2.2+    , generic-lens        >=1.1  && <2.3     , heddit-    , microlens-platform  ^>=0.4.2+    , microlens-platform  ^>=0.4     , text                ^>=1.2    main-is:          Streaming.hs@@ -181,10 +182,10 @@     buildable: False    build-depends:-    , base                >=4.13  && <5-    , generic-lens        >=1.1   && <2.2+    , base                >=4.13 && <5+    , generic-lens        >=1.1  && <2.3     , heddit-    , microlens-platform  ^>=0.4.2+    , microlens-platform  ^>=0.4     , text                ^>=1.2    main-is:          Paginating.hs@@ -197,13 +198,13 @@   hs-source-dirs:   tests   build-depends:     , aeson         ^>=1.5-    , base          >=4.13   && <5+    , base          >=4.13 && <5     , bytestring    >=0.9     , containers    ^>=0.6-    , generic-lens  >=1.1    && <2.2+    , generic-lens  >=1.1  && <2.3     , heddit-    , hspec         >=2.0    && <3.0-    , microlens     ^>=0.4.10+    , hspec         >=2.0  && <3.0+    , microlens     ^>=0.4    main-is:          Main.hs   default-language: Haskell2010@@ -218,14 +219,14 @@   hs-source-dirs:   io-tests   build-depends:     , aeson          ^>=1.5-    , base           >=4.13   && <5+    , base           >=4.13 && <5     , containers     ^>=0.6-    , generic-lens   >=1.1    && <2.2+    , generic-lens   >=1.1  && <2.3     , heddit-    , hspec          >=2.0    && <3.0-    , microlens-ghc  ^>=0.4.10+    , hspec          >=2.0  && <3.0+    , microlens-ghc  ^>=0.4     , text           ^>=1.2-    , time           >=1.9    && <1.13+    , time           >=1.9  && <1.13     , unliftio       ^>=0.2    other-modules:
io-tests/LiveSpec.hs view
@@ -44,7 +44,7 @@     case us ^? _head of         Just update -> do             username <- mkUsername "pokoleo"-            update ^. #author `shouldBe` Just username+            update ^. #author `shouldBe` username             update ^. #body `shouldBe` "Woah, this still exists"             update ^. #stricken `shouldBe` False         _           -> expectationFailure "Failed to get updates"
io-tests/MeSpec.hs view
@@ -16,7 +16,7 @@  spec :: Spec spec = beforeAll loadClientAndProfile . describe "Network.Reddit.Me"-    $ sequence_ [ account, multireddits, karma, friends ]+    $ sequence_ [ account, multireddits, karma, friends, makeFriends ]  account :: SpecWith (Client, TestProfile) account = it "gets the current user" $ \(c, profile) -> do@@ -31,13 +31,25 @@  karma :: SpecWith (Client, TestProfile) karma = it "gets the current user's karma" $ \(c, _) -> do-    ms <- tryReddit @APIException c getMyKarma-    ms `shouldSatisfy` isRight+    k <- tryReddit @APIException c getMyKarma+    k `shouldSatisfy` isRight  friends :: SpecWith (Client, TestProfile) friends = it "gets the current user's friends" $ \(c, _) -> do-    ms <- tryReddit @APIException c getMyFriends-    ms `shouldSatisfy` isRight+    fs <- tryReddit @APIException c getMyFriends+    fs `shouldSatisfy` isRight++makeFriends :: SpecWith (Client, TestProfile)+makeFriends = it "makes friends with a user" $ \(c, _) -> do+    username <- mkUsername "heddit-dev"+    friend <- runReddit c $ makeFriend Nothing username+    friend2 <- runReddit c $ getMyFriend username+    friend2 `shouldBe` friend+    fs <- runReddit c getMyFriends+    fs ^.. each `shouldContain` [ friend ]+    runReddit c $ unFriend username+    fs2 <- runReddit c getMyFriends+    fs2 ^.. each `shouldNotContain` [ friend ]  loadClientAndProfile :: IO (Client, TestProfile) loadClientAndProfile = (,) <$> loadClient Nothing <*> loadProfile
io-tests/UserSpec.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE TypeApplications #-} @@ -14,7 +15,14 @@  spec :: Spec spec = beforeAll (loadClient Nothing) . describe "Network.Reddit.User"-    $ sequence_ [ users, trophies, submissions, summaries, searches ]+    $ sequence_ [ users+                , trophies+                , submissions+                , summaries+                , searches+                , moderated+                , banned+                ]  users :: SpecWith Client users = it "gets a user" $ \c -> do@@ -64,3 +72,20 @@         Just u  -> do             username <- mkUsername "edwardkmett"             u ^. #username `shouldBe` username++moderated :: SpecWith Client+moderated = it "searches users by name" $ \c -> do+    ms <- runReddit c $ getUserModerated =<< mkUsername "edwardkmett"+    subname <- mkSubredditName "haskell"+    (ms ^.. each . #name) `shouldContain` [ subname ]++banned :: SpecWith Client+banned =+    it "throws the correct exception when getting a banned user" $ \c -> do+        username <- mkUsername "Unidan"+        let action = getUser username+            ex     = \case+                UserIsBanned (BannedUser uname _ True)+                    | uname == username -> True+                _ -> False+        runReddit c action `shouldThrow` ex
src/Network/Reddit.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-}@@ -18,6 +19,13 @@ -- Stability   : experimental -- Portability : GHC --+-- @heddit@ provides Haskell bindings to Reddit\'s API. It aims to be as feature-+-- rich and comprehensive as libraries such as @praw@ for Python.+--+-- This module exports most of the functionality you will need to get started+-- with @heddit@, including authentication and actions\/types to work with users,+-- subreddits, submissions, and comments. For a more in-depth introduction, please+-- see the README in this repository or at <https://gitlab.com/ngua/heddit> module Network.Reddit     ( newClient     , newClientWithManager@@ -28,6 +36,7 @@     , tryReddit     , getRateLimits     , withRateLimitDelay+    , withReadOnly     , fileTokenManager       -- * Actions     , firstPage@@ -42,6 +51,8 @@     , Listing(Listing)     , Paginator(Paginator)     , Paginable+    , pattern DeletedUser+    , isUserDeleted     , ItemOpts(ItemOpts)     , defaultItemOpts     , ItemSort(..)@@ -67,6 +78,7 @@     , StatusMessage(StatusMessage)     , StatusCode     , POSTError(POSTError)+    , BannedUser(BannedUser)       -- * Auth     , ClientState     , AppType(..)@@ -127,6 +139,7 @@ import           Network.HTTP.Client.TLS   ( newTlsManager ) import           Network.Reddit.Auth import           Network.Reddit.Comment    as M+import           Network.Reddit.Internal import           Network.Reddit.Me         as M import           Network.Reddit.Submission as M import           Network.Reddit.Subreddit  as M@@ -298,6 +311,20 @@             sleep    = threadDelay $ round duration * 1000000         bool (pure ()) sleep $ duration > 0         action++-- | Run a 'MonadReddit' action in a read-only context, as if you were using+-- an 'ApplicationOnly' client+--+-- __Note__: To avoid cases where the current 'AccessToken' expires while running+-- an action in this environment, the token will be refreshed before running the+-- provided action+withReadOnly :: MonadReddit m => m a -> m a+withReadOnly action = do+    updateToken =<< liftIO getPOSIXTime+    local withAppOnly action+  where+    withAppOnly =+        field @"authConfig" . field @"appType" .~ ApplicationOnly mempty  -- | Transform an action producing a @Listing@ of items into an infinite stream. -- Items are pushed to the stream as they are fetched, with oldest items yielded
src/Network/Reddit/Internal.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-}@@ -22,6 +23,7 @@     , runAction_     , runActionWith     , runActionWith_+    , updateToken     , mkRequest     , getMany     , redditURL@@ -103,16 +105,19 @@     expiresIn <- readClientState $ field @"accessToken" . field @"expiresIn"     obtained <- readClientState $ field @"tokenObtained"     now <- liftIO getPOSIXTime-    when (now > (obtained - 10) + expiresIn) $ do-        newToken <- refreshAccessToken-        state <- asks (^. field @"clientState")-        atomicModifyIORef' state $ \s ->-            ( s-              & (field @"tokenObtained" .~ now)-              . (field @"accessToken" .~ newToken)-            , ()-            )+    when (now > (obtained - 10) + expiresIn) $ updateToken now +updateToken :: MonadReddit m => POSIXTime -> m ()+updateToken now = do+    newToken <- refreshAccessToken+    state <- asks (^. field @"clientState")+    atomicModifyIORef' state $ \s ->+        ( s+          & (field @"tokenObtained" .~ now)+          . (field @"accessToken" .~ newToken)+        , ()+        )+ -- | Update the current rate limit info, reading it from Reddit\'s response -- headers updateRateLimits :: MonadReddit m => Response (RawBody m) -> m ()@@ -251,6 +256,11 @@  -- NOTE -- This action needs to be in this module to avoid cyclic module dependencies--- | Get account information for the currently logged-in user+-- | Get account information for the currently logged-in user. If you are using+-- an 'ApplicationOnly' client, calling this will throw an 'InvalidRequest' getMe :: MonadReddit m => m Account-getMe = runAction defaultAPIAction { pathSegments = [ "api", "v1", "me" ] }+getMe = asks (^. field @"authConfig" . field @"appType") >>= \case+    ApplicationOnly {} ->+        throwM $ InvalidRequest "getMe: This action requires a user context"+    _                  ->+        runAction defaultAPIAction { pathSegments = [ "api", "v1", "me" ] }
src/Network/Reddit/Me.hs view
@@ -22,16 +22,21 @@     , getPreferences     , updatePreferences     , getMySubreddit+    , getMySubredditSettings+    , setMySubredditSettings     , getMyOverview     , getMySaved     , getMyComments     , getMySubmissions     , getMyHidden     , getMyFriends+    , getMyFriend     , getMyBlocked     , getMyKarma     , makeFriend     , unFriend+    , trustUser+    , distrustUser     , blockUser     , needsCaptcha     , getMyFlair@@ -40,7 +45,6 @@     , getMyModerated     , getMyContributing     , getMyMultireddits-    , getMySubredditSettings     ) where  import           Control.Monad.Catch@@ -99,13 +103,17 @@     getSubreddit =<< mkSubredditName (usernameToDisplayName username)  -- | Get settings for the special user 'Subreddit' associated with the authenticated--- account. In order to update the settings, you can use 'setSubredditSettings'--- directly, as it requires the subreddit ID+-- account. Also see 'setMySubredditSettings' getMySubredditSettings :: MonadReddit m => m SubredditSettings getMySubredditSettings = do     Account { username } <- getMe     getSubredditSettings =<< mkSubredditName (usernameToDisplayName username) +-- | Set the 'SubredditSettings' for your special user subreddit. You can obtain the+-- existing settings using 'getMySubredditSettings'+setMySubredditSettings :: MonadReddit m => SubredditSettings -> m ()+setMySubredditSettings = setSubredditSettings+ -- | Get an overview of the authenticated user\'s 'Comment's and 'Submission's getMyOverview     :: MonadReddit m => Paginator ItemID Item -> m (Listing ItemID Item)@@ -149,6 +157,14 @@   where     r = defaultAPIAction { pathSegments = mePath [ "friends" ] } +-- | Get information about one of the authenticated user\'s friends. You must be+-- friends with the owner of the provided username, or an 'POSTError' will be+-- thrown+getMyFriend :: MonadReddit m => Username -> m Friend+getMyFriend uname =+    runAction defaultAPIAction+              { pathSegments = mePath [ "friends", toUrlPiece uname ] }+ -- | Get blocked users (as 'Friend's) of the currently logged-in user getMyBlocked :: MonadReddit m => m (Seq Friend) getMyBlocked = runAction @FriendList r <&> wrappedTo@@ -161,8 +177,13 @@   where     r = defaultAPIAction { pathSegments = mePath [ "karma" ] } --- | Make friends with another user-makeFriend :: MonadReddit m => Maybe Text -> Username -> m Friend+-- | Make friends with another user. You must have Reddit premium to add a note+-- to the friendship. If you attempt to add the note without premium, you are+-- likely to get an 'APIException' ('POSTError')+makeFriend :: MonadReddit m+           => Maybe Text -- ^ Providing this requires Reddit premium+           -> Username+           -> m Friend makeFriend note uname =     runAction defaultAPIAction               { method       = PUT@@ -178,6 +199,23 @@     runAction_ defaultAPIAction                { pathSegments = mePath [ "friends", toUrlPiece uname ]                , method       = DELETE+               }++-- | Add the user to your list of trusted users who may always send you PMs+trustUser :: MonadReddit m => Username -> m ()+trustUser = trustDistrust "add_whitelisted"++-- | Remove the user from your list of trusted users+distrustUser :: MonadReddit m => Username -> m ()+distrustUser = trustDistrust "remove_whitelisted"++trustDistrust :: MonadReddit m => PathSegment -> Username -> m ()+trustDistrust path uname =+    runAction_ defaultAPIAction+               { pathSegments = [ "api", path ]+               , method       = POST+               , requestData  =+                     mkTextFormData [ ("name", toQueryParam uname) ]                }  -- | Block another user. Note that this cannot be reversed through the API; the
src/Network/Reddit/Moderation.hs view
@@ -137,6 +137,7 @@     , updateSubredditRule     , reorderSubredditRules       -- * Flair+    , configureSubredditFlair     , getFlairList     , getUserFlair     , setUserFlair@@ -269,6 +270,11 @@ import           Network.Reddit.Types.Comment import           Network.Reddit.Types.Emoji import           Network.Reddit.Types.Flair+import           Network.Reddit.Types.Flair            as M+                 ( FlairConfig(FlairConfig)+                 , FlairPosition(..)+                 , defaultFlairConfig+                 ) import           Network.Reddit.Types.Item import           Network.Reddit.Types.Moderation import           Network.Reddit.Types.Moderation       as M@@ -1047,13 +1053,12 @@  -- | Configure a subreddit with the provided 'SubredditSettings' setSubredditSettings :: MonadReddit m => SubredditSettings -> m ()-setSubredditSettings ss = runAction_ r-  where-    r = defaultAPIAction-        { pathSegments = [ "api", "site_admin" ]-        , method       = POST-        , requestData  = WithForm $ toForm ss-        }+setSubredditSettings ss =+    runAction_ defaultAPIAction+               { pathSegments = [ "api", "site_admin" ]+               , method       = POST+               , requestData  = WithForm $ toForm ss+               }  --Subreddit rules-------------------------------------------------------------- -- | Add a rule to the subreddit. The newly created 'SubredditRule' is returned@@ -1127,6 +1132,15 @@  --Flair------------------------------------------------------------------------ -- | Get a list of usernames and the flair currently assigned to them+configureSubredditFlair+    :: MonadReddit m => SubredditName -> FlairConfig -> m ()+configureSubredditFlair sname fc =+    runAction_ defaultAPIAction+               { pathSegments = subAPIPath sname "flairconfig"+               , method       = POST+               , requestData  = WithForm $ toForm fc+               }+ getFlairList :: MonadReddit m              => SubredditName              -> Paginator UserID AssignedFlair
src/Network/Reddit/Types/Account.hs view
@@ -22,9 +22,7 @@ -- Portability : GHC -- module Network.Reddit.Types.Account-    ( Username-    , mkUsername-    , UserID(UserID)+    ( UserID(UserID)     , Account(..)     , AccountSearchOpts(..)     , AccountSearchSort(..)@@ -34,6 +32,7 @@     , KarmaList     , UserSummary(..)     , UserSummaryList+    , UserModeratedList     , Preferences(..)     , MediaPreference(..)     , AcceptPMs(..)@@ -41,7 +40,6 @@     ) where  import           Control.Monad                  ( (<=<) )-import           Control.Monad.Catch            ( MonadThrow )  import           Data.Aeson                  ( (.!=)@@ -82,22 +80,6 @@ import           Web.FormUrlEncoded             ( ToForm(toForm) ) import           Web.HttpApiData                ( ToHttpApiData(..) ) --- | Reddit username-newtype Username = Username Text-    deriving stock ( Show, Generic )-    deriving newtype ( FromJSON, ToJSON, ToHttpApiData )-    deriving ( Eq ) via CIText Username---- | Smart constructor for 'Username', which must be between 3 and 20 chars,--- and may only include upper\/lowercase alphanumeric chars, underscores, or--- hyphens-mkUsername :: MonadThrow m => Text -> m Username-mkUsername = validateName Nothing Nothing "Username"---- | Prefix the username with \"u_\"-usernameToDisplayName :: Username -> Text-usernameToDisplayName (Username uname) = "u_" <> uname- -- | A unique, site-wide ID for an account newtype UserID = UserID Text     deriving stock ( Show, Generic )@@ -288,7 +270,7 @@         <*> o .: "comment_karma"         <*> o .: "link_karma"         <*> (integerToUTC <$> o .: "created_utc")-        <*> (o .: "profile_img")+        <*> o .: "profile_img"         <*> (nothingTxtNull =<< o .: "profile_color")         <*> o .: "profile_over_18" @@ -302,9 +284,18 @@         $ fmap (UserSummaryList . fromList) . userSummariesP       where         userSummariesP o = for (HM.toList o) $ \(usid, us) -> do-            uid <- parseJSON (String usid)+            uid <- parseJSON $ String usid             u <- parseJSON us             pure $ u & field @"userID" ?~ uid++-- | Wrapper for parsing the JSON returned when getting the moderated subreddits+-- of a user+newtype UserModeratedList = UserModeratedList (Seq Subreddit)+    deriving stock ( Show, Generic )++instance FromJSON UserModeratedList where+    parseJSON = withKind @Array ModeratedListKind "UserModeratedList"+        $ fmap (UserModeratedList . fromList) . traverse parseJSON . toList  -- | User preferences data Preferences = Preferences
src/Network/Reddit/Types/Comment.hs view
@@ -53,7 +53,6 @@  import           Lens.Micro -import           Network.Reddit.Types.Account import           Network.Reddit.Types.Award import           Network.Reddit.Types.Internal import           Network.Reddit.Types.Submission ( SubmissionID )@@ -144,7 +143,7 @@     repliesP _            = mempty  instance Paginable Comment where-    type PaginateOptions Comment = ItemOpts +    type PaginateOptions Comment = ItemOpts      type PaginateThing Comment = CommentID 
src/Network/Reddit/Types/Flair.hs view
@@ -38,6 +38,9 @@     , FlairContent(..)     , FlairType(..)     , CSSClass+    , FlairConfig(..)+    , FlairPosition(..)+    , defaultFlairConfig     ) where  import           Control.Monad.Catch            ( MonadThrow(throwM) )@@ -127,8 +130,8 @@     deriving stock ( Show, Eq, Generic )  instance FromJSON FlairList where-    parseJSON = withObject "FlairList" $ \o ->-        FlairList <$> (o .:? "prev") <*> (o .:? "next") <*> (o .: "users")+    parseJSON = withObject "FlairList"+        $ \o -> FlairList <$> o .:? "prev" <*> o .:? "next" <*> o .: "users"  -- | Convert a 'FlairList' to a 'Listing', allowing it to be used with other -- functions/actions expecting a listing@@ -336,3 +339,49 @@  instance ToHttpApiData ForegroundColor where     toQueryParam = showTextData++-- | Configuration options for subreddit flair, including both user and submission+-- flair. Also see 'defaultFlairConfig'+data FlairConfig = FlairConfig+    { enabled        :: Bool+      -- | Allow user flair self-assignment+    , selfAssign     :: Bool+      -- | Allow submission flair self-assignment+    , linkSelfAssign :: Bool+      -- | Position of user flair+    , position       :: FlairPosition+      -- | Position of submission flair+    , linkPosition   :: FlairPosition+    }+    deriving stock ( Show, Eq, Generic )++instance ToForm FlairConfig where+    toForm FlairConfig { .. } =+        fromList [ ("flair_enabled", toQueryParam enabled)+                 , ("flair_self_assign_enabled", toQueryParam selfAssign)+                 , ( "link_flair_self_assign_enabled"+                   , toQueryParam linkSelfAssign+                   )+                 , ("flair_position", toQueryParam position)+                 , ("link_flair_position", toQueryParam linkPosition)+                 , ("api_type", "json")+                 ]++-- | A 'FlairConfig' with default values+defaultFlairConfig :: FlairConfig+defaultFlairConfig = FlairConfig+    { enabled        = True+    , selfAssign     = False+    , linkSelfAssign = False+    , position       = OnRight+    , linkPosition   = OnLeft+    }++-- | Position for subreddit flair+data FlairPosition+    = OnLeft+    | OnRight+    deriving stock ( Show, Eq, Generic )++instance ToHttpApiData FlairPosition where+    toQueryParam = T.drop 2 . showTextData
src/Network/Reddit/Types/Internal.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DerivingVia #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-}@@ -10,8 +10,8 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-}- {-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RoleAnnotations #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -37,6 +37,11 @@     , Paginable(..)     , Paginator(..)     , Listing(..)+    , Username+    , pattern DeletedUser+    , mkUsername+    , usernameToDisplayName+    , isUserDeleted     , CIText(CIText)     , HKD     , ItemOpts(..)@@ -66,6 +71,7 @@     , StatusCode     , StatusMessage(..)     , POSTError(..)+    , BannedUser(..)       -- * Utilities     , dropTypePrefix     , integerToUTC@@ -90,7 +96,7 @@  import           Conduit                      ( ConduitM ) -import           Control.Applicative          ( optional )+import           Control.Applicative          ( Alternative((<|>)), optional ) import           Control.Exception            ( Exception(..), SomeException ) import           Control.Monad                ( guard ) import           Control.Monad.Catch          ( MonadThrow(throwM) )@@ -175,6 +181,7 @@     | ModActionKind -- @modaction@     | LiveThreadKind -- @LiveUpdateEvent@     | LiveUpdateKind -- @LiveUpdate@+    | ModeratedListKind -- @ModeratedList@     deriving stock ( Eq )  instance FromJSON RedditKind where@@ -200,6 +207,7 @@         "modaction" -> pure ModActionKind         "LiveUpdateEvent" -> pure LiveThreadKind         "LiveUpdate" -> pure LiveUpdateKind+        "ModeratedList" -> pure ModeratedListKind         _ -> mempty  -- | \"Thing\"s are the base class of Reddit's OOP model. Each thing has several@@ -298,9 +306,8 @@         commonOpts = mkTextForm             $ [ ("limit", tshow limit) ]             <> catMaybes [ ("show", ) <$> bool Nothing (Just "given") showAll-                         , asum [ ("after", ) . fullname <$> after-                                , ("before", ) . fullname <$> before-                                ]+                         , (("after", ) . fullname <$> after)+                           <|> (("before", ) . fullname <$> before)                          ]  instance {-# OVERLAPPING #-}( GL.HasField' name (Paginator t a) s@@ -468,6 +475,33 @@ instance ToHttpApiData SubredditType where     toQueryParam = showTextData +-- | Reddit username+newtype Username = Username Text+    deriving stock ( Show, Generic )+    deriving newtype ( FromJSON, ToJSON, ToHttpApiData )+    deriving ( Eq ) via CIText Username++-- | Smart constructor for 'Username', which must be between 3 and 20 chars,+-- and may only include upper\/lowercase alphanumeric chars, underscores, or+-- hyphens+mkUsername :: MonadThrow m => Text -> m Username+mkUsername = validateName Nothing Nothing "Username"++-- | Prefix the username with \"u_\"+usernameToDisplayName :: Username -> Text+usernameToDisplayName (Username uname) = "u_" <> uname++-- | Pattern for \"[deleted]\" username+pattern DeletedUser :: Username+pattern DeletedUser = Username "[deleted]"++-- | Test if a user has the \"[deleted]\" username. Also see the 'DeletedUser'+-- pattern synonym+isUserDeleted :: Username -> Bool+isUserDeleted = \case+    DeletedUser -> True+    _           -> False+ -- | Type synonym for URLs type URL = Text @@ -530,6 +564,9 @@     | Redirected (Maybe Request)       -- ^ If the API action should not allow automatic redirects,       -- this error returns the possible redirected request+    | UserIsBanned BannedUser+      -- ^ Thrown when attempting to get account information on a banned+      -- user     | WebsocketError Text SomeException       -- ^ Thrown when exceptions occur during websocket handling     | UploadFailed@@ -547,6 +584,7 @@              , ErrorWithMessage <$> parseJSON o              , InvalidPOST <$> parseJSON o              , InvalidCredentials <$> parseJSON o+             , UserIsBanned <$> parseJSON o              ]  -- | An error which occurs when attempting to authenticate via OAuth@@ -639,6 +677,22 @@         <*> o .: "message"         <*> o .: "reason" +-- | Information about a banned user+data BannedUser = BannedUser+    { name        :: Username+    , totalKarma  :: Integer+      -- | Presumably, this should always be @True@. Reddit includes+      -- it in the JSON response nonetheless+    , isSuspended :: Bool+    }+    deriving stock ( Show, Eq, Generic )++instance FromJSON BannedUser where+    parseJSON = withKind AccountKind "BannedUser" $ \o -> BannedUser+        <$> o .: "name"+        <*> o .: "total_karma"+        <*> o .: "is_suspended"+ redditExToException :: Exception e => e -> SomeException redditExToException = toException . RedditException @@ -723,6 +777,7 @@     ModActionKind         -> "modaction"     LiveThreadKind        -> "LiveUpdateEvent"     LiveUpdateKind        -> "LiveUpdate"+    ModeratedListKind     -> "ModeratedList"  -- | Parse the @edited@ field in comments or submissions, which can either be -- @false@ or a Unix timestamp
src/Network/Reddit/Types/Live.hs view
@@ -40,7 +40,8 @@ import           Control.Monad                 ( (<=<) )  import           Data.Aeson-                 ( (.:)+                 ( (.!=)+                 , (.:)                  , (.:?)                  , FromJSON(parseJSON)                  , Options(constructorTagModifier)@@ -126,7 +127,7 @@  instance FromJSON LiveThreadID where     parseJSON =-        withText "LiveThreadID" (coerce . dropTypePrefix LiveThreadKind)+        withText "LiveThreadID" $ coerce . dropTypePrefix LiveThreadKind  instance Thing LiveThreadID where     fullname (LiveThreadID ltid) = prependType LiveThreadKind ltid@@ -195,7 +196,7 @@ -- | An individual update in a 'LiveThread' data LiveUpdate = LiveUpdate     { liveUpdateID :: LiveUpdateID-    , author       :: Maybe Username+    , author       :: Username     , body         :: Body     , bodyHTML     :: Body     , stricken     :: Bool@@ -206,7 +207,7 @@ instance FromJSON LiveUpdate where     parseJSON = withKind LiveUpdateKind "LiveUpdate" $ \o -> LiveUpdate         <$> o .: "name"-        <*> o .: "author"+        <*> o .: "author" .!= DeletedUser         <*> o .: "body"         <*> o .: "body_html"         <*> o .: "stricken"@@ -236,7 +237,7 @@  instance FromJSON LiveUpdateID where     parseJSON =-        withText "LiveUpdateID" (coerce . dropTypePrefix LiveUpdateKind)+        withText "LiveUpdateID" $ coerce . dropTypePrefix LiveUpdateKind  -- | External resources embedded in a 'LiveUpdate' data LiveUpdateEmbed = LiveUpdateEmbed
src/Network/Reddit/Types/Message.hs view
@@ -50,7 +50,6 @@  import           Lens.Micro -import           Network.Reddit.Types.Account import           Network.Reddit.Types.Comment  ( CommentID ) import           Network.Reddit.Types.Internal 
src/Network/Reddit/Types/Multireddit.hs view
@@ -57,7 +57,6 @@ import           GHC.Exts                       ( IsList(fromList, toList) ) import           GHC.Generics                   ( Generic ) -import           Network.Reddit.Types.Account import           Network.Reddit.Types.Internal import           Network.Reddit.Types.Subreddit 
src/Network/Reddit/Types/Submission.hs view
@@ -112,7 +112,6 @@ import           GHC.Exts                       ( IsList(..) ) import           GHC.Generics                   ( Generic ) -import           Network.Reddit.Types.Account import           Network.Reddit.Types.Award import           Network.Reddit.Types.Flair import           Network.Reddit.Types.Internal
src/Network/Reddit/Types/Subreddit.hs view
@@ -138,13 +138,12 @@     deriving stock ( Show, Eq, Generic )  instance FromJSON Subreddit where-    parseJSON v =-        asum [ withKind SubredditKind "Subreddit" (subredditP "id") v-             , withObject "Subreddit" (subredditP "name") v-             ]+    parseJSON v = asum [ withKind SubredditKind "Subreddit" subredditP v+                       , withObject "Subreddit" subredditP v+                       ]       where-        subredditP idField o = do-            subredditID <- o .: idField+        subredditP o = do+            subredditID <- o .: "id" <|> o .: "name"             name <- o .: "display_name"             title <- o .: "title"             -- This field is only missing when getting a user's subreddit.@@ -153,35 +152,35 @@             created <- integerToUTC <$> o .:? "created_utc" .!= 0             subredditType <- o .: "subreddit_type"             keyColor <- nothingTxtNull =<< o .: "key_color"-            description <- o .: "description"+            description <- o .:? "description" .!= mempty             descriptionHTML <- o .:? "description_html"-            publicDescription <- o .: "public_description"-            submitTextLabel <- o .: "submit_text_label"+            publicDescription <- o .:? "public_description" .!= mempty+            submitTextLabel <- o .:? "submit_text_label"             submitText <- o .:? "submit_text"             submitTextHTML <- o .:? "submit_text_html"             subscribers <- o .: "subscribers"             iconImg <- maybe (pure Nothing) nothingTxtNull                 =<< o .:? "icon_img"             bannerImg <- maybe (pure Nothing) nothingTxtNull-                =<< o .: "banner_img"+                =<< o .:? "banner_img"             bannerSize <- o .:? "banner_size"             headerImg <- o .:? "header_img"             headerSize <- o .:? "header_size"             over18 <- o .: "over_18" <|> o .: "over18"-            quarantine <- o .: "quarantine"-            userIsBanned <- o .: "user_is_banned"-            userIsMuted <- o .: "user_is_muted"-            userIsModerator <- o .: "user_is_moderator"-            userIsContributor <- o .: "user_is_contributor"+            quarantine <- o .:? "quarantine" .!= False+            userIsBanned <- o .:? "user_is_banned" .!= False+            userIsMuted <- o .:? "user_is_muted".!= False+            userIsModerator <- o .:? "user_is_moderator".!= False+            userIsContributor <- o .:? "user_is_contributor".!= False             userIsSubscriber <- o .: "user_is_subscriber"             allowImages <- o .:? "allow_images" .!= True             allowVideos <- o .:? "allow_videos" .!= True             allowPolls <- o .:? "allow_polls" .!= True             allowVideoGIFs <- o .:? "allow_videogifs" .!= True-            freeFormReports <- o .: "free_form_reports"-            restrictPosting <- o .: "restrict_posting"-            restrictCommenting <- o .: "restrict_commenting"-            linkFlairEnabled <- o .: "link_flair_enabled"+            freeFormReports <- o .:? "free_form_reports" .!= True+            restrictPosting <- o .:? "restrict_posting".!= False+            restrictCommenting <- o .:? "restrict_commenting".!= False+            linkFlairEnabled <- o .:? "link_flair_enabled".!= False             canAssignLinkFlair <- o .:? "can_assign_link_flair"             canAssignUserFlair <- o .:? "can_assign_user_flair"             spoilersEnabled <- o .:? "spoilers_enabled"
src/Network/Reddit/Types/Widget.hs view
@@ -108,7 +108,6 @@  import           Lens.Micro -import           Network.Reddit.Types.Account import           Network.Reddit.Types.Flair import           Network.Reddit.Types.Internal import           Network.Reddit.Types.Subreddit
src/Network/Reddit/Types/Wiki.hs view
@@ -45,7 +45,6 @@ import           GHC.Exts                      ( IsList(toList, fromList) ) import           GHC.Generics                  ( Generic ) -import           Network.Reddit.Types.Account import           Network.Reddit.Types.Internal  import           Web.HttpApiData               ( ToHttpApiData(..) )
src/Network/Reddit/User.hs view
@@ -30,6 +30,8 @@     , getUserGilded     , getUserSaved     , getUserMultireddits+    , getUserSubreddit+    , getUserModerated     , gildUser       -- ** User @Listing@s and summaries       -- | These actions return @Listing@s for special user subreddits,@@ -44,7 +46,7 @@     , Trophy     ) where -import           Control.Monad                    ( unless )+import           Control.Monad                    ( (<=<), unless ) import           Control.Monad.Catch              ( MonadThrow(throwM) )  import           Data.Aeson                       ( FromJSON )@@ -59,6 +61,7 @@ import           Lens.Micro  import           Network.Reddit.Internal+import           Network.Reddit.Subreddit import           Network.Reddit.Types import           Network.Reddit.Types.Account import           Network.Reddit.Types.Account     as M@@ -72,15 +75,16 @@                  , Preferences(Preferences)                  , UserID(UserID)                  , UserSummary(UserSummary)-                 , Username-                 , mkUsername                  ) import           Network.Reddit.Types.Award import           Network.Reddit.Types.Comment+import           Network.Reddit.Types.Internal    as M+                 ( Username+                 , mkUsername+                 ) import           Network.Reddit.Types.Item import           Network.Reddit.Types.Multireddit import           Network.Reddit.Types.Submission-import           Network.Reddit.Types.Subreddit import           Network.Reddit.Utils  import           Web.FormUrlEncoded               ( ToForm(toForm) )@@ -172,6 +176,20 @@ getUserMultireddits uname =     runAction defaultAPIAction               { pathSegments = [ "api", "multi", "user", toUrlPiece uname ] }++-- | Get the special user 'Subreddit' for the given username+getUserSubreddit :: MonadReddit m => Username -> m Subreddit+getUserSubreddit = getSubreddit <=< mkSubredditName . usernameToDisplayName++-- | Get a list of the subreddits that a user moderates+--+-- __Note__: Strangely, this will not include the user\'s special user subreddit+getUserModerated :: MonadReddit m => Username -> m (Seq Subreddit)+getUserModerated uname = runAction @UserModeratedList r <&> wrappedTo+  where+    r = defaultAPIAction+        { pathSegments = [ "user", toUrlPiece uname, "moderated_subreddits" ]+        }  -- | Give the user Reddit gold, optionally specifying the duration gildUser :: MonadReddit m