packages feed

heddit-0.0.1: src/Network/Reddit/Live.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TypeApplications #-}

-- |
-- Module      : Network.Reddit.Live
-- Copyright   : (c) 2021 Rory Tyler Hayford
-- License     : BSD-3-Clause
-- Maintainer  : rory.hayford@protonmail.com
-- Stability   : experimental
-- Portability : GHC
--
-- Actions for working with 'LiveThread's
--
module Network.Reddit.Live
    ( -- * Actions
      getLiveThread
    , getLiveInfo
    , getAllLiveInfo
    , getLiveUpdates
    , getLiveUpdate
    , getLiveDiscussions
    , getLiveContributors
    , reportLiveThread
    , createLiveThread
    , closeLiveThread
    , updateLiveThread
    , addLiveUpdate
    , strikeLiveUpdate
    , deleteLiveUpdate
      -- ** Live thread contribution
    , removeLiveContributor
    , removeLiveContributorByName
    , updateLiveContributor
    , abdicateLiveContributor
    , inviteLiveContributor
    , inviteLiveContributorWithPerms
    , revokeLiveInvitation
    , revokeLiveInvitationByName
      -- * Types
    , module M
    ) where

import           Control.Monad.Catch             ( MonadThrow(throwM) )

import qualified Data.Foldable                   as F
import           Data.Generics.Product           ( HasField(field) )
import           Data.Generics.Wrapped           ( wrappedTo )
import           Data.List.Split                 ( chunksOf )
import           Data.Sequence                   ( Seq((:<|)) )
import           Data.Traversable                ( for )

import           Lens.Micro

import           Network.Reddit.Internal
import           Network.Reddit.Types
import           Network.Reddit.Types.Account
import           Network.Reddit.Types.Live
import           Network.Reddit.Types.Live       as M
                 ( LiveContributor(LiveContributor)
                 , LivePermission(..)
                 , LiveReportType(..)
                 , LiveState(..)
                 , LiveThread(LiveThread)
                 , LiveThreadID(LiveThreadID)
                 , LiveUpdate(LiveUpdate)
                 , LiveUpdateEmbed(LiveUpdateEmbed)
                 , LiveUpdateID(LiveUpdateID)
                 , NewLiveThread
                 , PostableLiveThread(PostableLiveThread)
                 , UpdatedLiveThread
                 , liveThreadToPostable
                 , mkNewLiveThread
                 )
import           Network.Reddit.Types.Submission
import           Network.Reddit.User
import           Network.Reddit.Utils

import           Web.FormUrlEncoded              ( ToForm(toForm) )
import           Web.HttpApiData                 ( ToHttpApiData(..) )
import           Web.Internal.FormUrlEncoded     ( Form )

-- | Get the details on a single 'LiveThread' given its ID
getLiveThread :: MonadReddit m => LiveThreadID -> m LiveThread
getLiveThread ltid =
    runAction defaultAPIAction
              { pathSegments = liveThreadPath [ toQueryParam ltid, "about" ] }

-- | Get information about live threads corresponding to each of the
-- 'LiveThreadID's in the given container. Invalid IDs are silently discarded
-- by this endpoint
--
-- __Note__: This endpoint will only accept a maximum of 100 'LiveThreadID's. If
-- you would like to get all of the information for a larger number of 'LiveThread's
-- at once, see 'getAllLiveInfo'
getLiveInfo :: (MonadReddit m, Foldable t)
            => t LiveThreadID
            -> Paginator LiveThreadID LiveThread
            -> m (Listing LiveThreadID LiveThread)
getLiveInfo ltids paginator =
    runAction defaultAPIAction
              { pathSegments = liveThreadPath [ "by_id", joinParams ltids ]
              , requestData  = paginatorToFormData paginator
              }

-- | Get all of the 'LiveThread's corresponding to a container of 'LiveThreadID's,
-- without a limit
getAllLiveInfo
    :: (MonadReddit m, Traversable t) => t LiveThreadID -> m (Seq LiveThread)
getAllLiveInfo ltids = fmap mconcat . for (chunked ltids) $ \ls ->
    getLiveInfo ls emptyPaginator { limit = apiRequestLimit }
    <&> (^. field @"children")
  where
    chunked = chunksOf apiRequestLimit . F.toList

-- | Get a @Listing@ of 'LiveUpdate's for the given live thread
getLiveUpdates :: MonadReddit m
               => LiveThreadID
               -> Paginator LiveUpdateID LiveUpdate
               -> m (Listing LiveUpdateID LiveUpdate)
getLiveUpdates ltid paginator =
    runAction defaultAPIAction
              { pathSegments = [ "live", toQueryParam ltid ]
              , requestData  = paginatorToFormData paginator
              }

-- | Get a single 'LiveUpdate' for the given live thread
getLiveUpdate :: MonadReddit m => LiveThreadID -> LiveUpdateID -> m LiveUpdate
getLiveUpdate ltid luid = do
    Listing { children } <- runAction @(Listing LiveUpdateID LiveUpdate) r
    case children of
        liveUpdate :<| _ -> pure liveUpdate
        _                -> throwM
            $ InvalidResponse "getLiveUpdate: No matching live update found"
  where
    r = defaultAPIAction
        { pathSegments =
              [ "live", toQueryParam ltid, "updates", toUrlPiece luid ]
        }

-- | Get a @Listing@ of 'Submission's representing the discussions on the given
-- live thread
getLiveDiscussions :: MonadReddit m
                   => LiveThreadID
                   -> Paginator SubmissionID Submission
                   -> m (Listing SubmissionID Submission)
getLiveDiscussions ltid paginator =
    runAction defaultAPIAction
              { pathSegments =
                    liveThreadPath [ toQueryParam ltid, "discussions" ]
              , requestData  = paginatorToFormData paginator
              }

-- | Get a list of contributors to the live thread
getLiveContributors
    :: MonadReddit m => LiveThreadID -> m (Seq LiveContributor)
getLiveContributors ltid = runAction @LiveContributorList r <&> wrappedTo
  where
    r = defaultAPIAction
        { pathSegments = liveThreadPath [ toQueryParam ltid, "contributors" ]
        }

-- | Report the given live thread to Reddit admins with the provided reason
reportLiveThread :: MonadReddit m => LiveReportType -> LiveThreadID -> m ()
reportLiveThread lrt ltid =
    runAction_ defaultAPIAction
               { pathSegments = liveThreadPath [ toQueryParam ltid, "report" ]
               , method       = POST
               , requestData  = mkTextFormData [ ("type", toQueryParam lrt)
                                               , ("api_type", "json")
                                               ]
               }

-- | Create a 'NewLiveThread', returning the 'LiveThread' upon success. Also see
-- 'mkNewLiveThread'
createLiveThread :: MonadReddit m => NewLiveThread -> m LiveThread
createLiveThread nlt = getLiveThread
    =<< (runAction @PostedLiveThread r <&> wrappedTo)
  where
    r = defaultAPIAction
        { pathSegments = liveThreadPath [ "create" ]
        , method       = POST
        , requestData  = WithForm $ toForm nlt
        }

-- | Close an existing live thread. After closing, it is no longer possible to
-- update or modify the live thread
--
-- __Warning__: This action is irreversible
closeLiveThread :: MonadReddit m => LiveThreadID -> m ()
closeLiveThread ltid =
    runAction_ defaultAPIAction
               { pathSegments =
                     liveThreadPath [ toQueryParam ltid, "close_thread" ]
               , method       = POST
               , requestData  = mkTextFormData [ ("api_type", "json") ]
               }

-- | Update the existing live thread with new settings
updateLiveThread :: MonadReddit m => LiveThreadID -> UpdatedLiveThread -> m ()
updateLiveThread ltid ult =
    runAction defaultAPIAction
              { pathSegments = liveThreadPath [ toQueryParam ltid, "edit" ]
              , method       = POST
              , requestData  = WithForm $ toForm ult
              }

-- | Add an update to the live thread
addLiveUpdate :: MonadReddit m => LiveThreadID -> Body -> m ()
addLiveUpdate ltid b =
    runAction_ defaultAPIAction
               { pathSegments = liveThreadPath [ toQueryParam ltid, "update" ]
               , method       = POST
               , requestData  =
                     mkTextFormData [ ("body", b), ("api_type", "json") ]
               }

-- | Strike the existing 'LiveUpdate', causing its @stricken@ field to be @True@
-- and the content to be crossed-out and marked incorrect on the web UI
strikeLiveUpdate :: MonadReddit m => LiveThreadID -> LiveUpdateID -> m ()
strikeLiveUpdate = strikeDeleteUpdate "strike_update"

-- | Strike the existing 'LiveUpdate', causing its @stricken@ field to be @True@
-- and the content to be crossed-out and marked incorrect on the web UI
deleteLiveUpdate :: MonadReddit m => LiveThreadID -> LiveUpdateID -> m ()
deleteLiveUpdate = strikeDeleteUpdate "delete_update"

strikeDeleteUpdate
    :: MonadReddit m => PathSegment -> LiveThreadID -> LiveUpdateID -> m ()
strikeDeleteUpdate path ltid luid =
    runAction_ defaultAPIAction
               { pathSegments = liveThreadPath [ toQueryParam ltid, path ]
               , method       = POST
               , requestData  = mkTextFormData [ ("id", fullname luid)
                                               , ("api_type", "json")
                                               ]
               }

-- | Remove the user as a contributor to the live thread. If you don\'t know the
-- contributor\'s user ID, you can use 'removeLiveContributorByName'
removeLiveContributor :: MonadReddit m => LiveThreadID -> UserID -> m ()
removeLiveContributor ltid uid =
    runAction defaultAPIAction
              { pathSegments = liveThreadPath [ toQueryParam ltid
                                              , "remove_live_contributor"
                                              ]
              , requestData  = mkTextFormData [ ("id", fullname uid) ]
              }

-- | Remove the live contributor by username. Note that this action must perform
-- an additional network request to fetch the user ID from the given username
removeLiveContributorByName
    :: MonadReddit m => LiveThreadID -> Username -> m ()
removeLiveContributorByName ltid uname = do
    Account { userID } <- getUser uname
    removeLiveContributor ltid userID

-- | Update the permissions for the live contributor
updateLiveContributor
    :: (MonadReddit m, Foldable t)
    => Maybe (t LivePermission)
    -- ^ If @Nothing@, grants all contributor permissions. If @Just@ but empty,
    -- removes all permissions
    -> LiveThreadID
    -> Username
    -> m ()
updateLiveContributor perms ltid uname =
    runAction_ defaultAPIAction
               { pathSegments =
                     [ toQueryParam ltid, "set_contributor_permissions" ]
               , method       = POST
               , requestData  =
                     mkTextFormData [ ("name", toQueryParam uname)
                                    , ("type", "liveupdate_contributor")
                                    , ( "permissions"
                                      , maybe "+all" joinPerms perms
                                      )
                                    , ("api_type", "json")
                                    ]
               }

-- | Abdicate your role as a live contributor, removing all access and permissions
--
-- __Warning__: This cannot be undone, even if you are the creator of the live
-- thread
abdicateLiveContributor :: MonadReddit m => LiveThreadID -> m ()
abdicateLiveContributor ltid =
    runAction_ defaultAPIAction
               { pathSegments =
                     liveThreadPath [ toQueryParam ltid, "leave_contributor" ]
               , method       = POST
               , requestData  = mkTextFormData [ ("api_type", "json") ]
               }

-- | Invite a user to contribute to the live thread. Note that this implicitly
-- grants all permissions to the invitee. If you would like more fine-grained
-- control over permissions, see 'inviteLiveContributorWithPerms'
inviteLiveContributor :: MonadReddit m => LiveThreadID -> Username -> m ()
inviteLiveContributor =
    inviteContributors $ mkTextForm [ ("permissions", "+all") ]

-- | As 'inviteLiveContributor', but allows customization of the permissions
-- granted to the invitee
inviteLiveContributorWithPerms
    :: (MonadReddit m, Foldable t)
    => t LivePermission -- ^ If empty, grants no permissions
    -> LiveThreadID
    -> Username
    -> m ()
inviteLiveContributorWithPerms perms =
    inviteContributors $ mkTextForm [ ("permissions", joinPerms perms) ]

-- | Revoke the invitation to contribute to the live thread. If you don\'t know
-- the contributor\'s user ID, you can use 'revokeLiveInvitationByName'
revokeLiveInvitation :: MonadReddit m => LiveThreadID -> UserID -> m ()
revokeLiveInvitation ltid uid =
    runAction_ defaultAPIAction
               { pathSegments = liveThreadPath [ toQueryParam ltid
                                               , "rm_contributor_invite"
                                               ]
               , method       = POST
               , requestData  = mkTextFormData [ ("id", fullname uid)
                                               , ("api_type", "json")
                                               ]
               }

-- | Revoke the live invitation by username. Note that this action must perform
-- an additional network request to fetch the user ID from the given username
revokeLiveInvitationByName
    :: MonadReddit m => LiveThreadID -> Username -> m ()
revokeLiveInvitationByName ltid uname = do
    Account { userID } <- getUser uname
    revokeLiveInvitation ltid userID

inviteContributors
    :: MonadReddit m => Form -> LiveThreadID -> Username -> m ()
inviteContributors form ltid uname =
    runAction_ defaultAPIAction
               { pathSegments = liveThreadPath [ toQueryParam ltid
                                               , "invite_contributor"
                                               ]
               , method       = POST
               , requestData  = WithForm
                     $ mkTextForm [ ("name", toQueryParam uname)
                                  , ("type", "liveupdate_contributor_invite")
                                  , ("api_type", "json")
                                  ]
                     <> form
               }

liveThreadPath :: [PathSegment] -> [PathSegment]
liveThreadPath ps = [ "api", "live" ] <> ps