module Network.Discord.Rest.Channel
(
ChannelRequest(..)
) where
import Control.Monad (when)
import Control.Concurrent.STM
import Control.Lens
import Control.Monad.Morph (lift)
import Data.Aeson
import Data.ByteString.Lazy
import Data.Hashable
import Data.Monoid ((<>))
import Data.Text
import Data.Time.Clock.POSIX
import Network.Wreq
import qualified Control.Monad.State as ST (get, liftIO)
import Network.Discord.Rest.Prelude
import Network.Discord.Types as Dc
data ChannelRequest a where
GetChannel :: Snowflake -> ChannelRequest Channel
ModifyChannel :: ToJSON a => Snowflake -> a -> ChannelRequest Channel
DeleteChannel :: Snowflake -> ChannelRequest Channel
GetChannelMessages :: Snowflake -> [(Text, Text)] -> ChannelRequest [Message]
GetChannelMessage :: Snowflake -> Snowflake -> ChannelRequest Message
CreateMessage :: Snowflake -> Text -> Maybe Embed -> ChannelRequest Message
UploadFile :: Snowflake -> Text -> ByteString -> ChannelRequest Message
EditMessage :: Message -> Text -> Maybe Embed -> ChannelRequest Message
DeleteMessage :: Message -> ChannelRequest ()
BulkDeleteMessage :: Snowflake -> [Message] -> ChannelRequest ()
EditChannelPermissions :: ToJSON a => Snowflake -> Snowflake -> a -> ChannelRequest ()
GetChannelInvites :: Snowflake -> ChannelRequest Object
CreateChannelInvite :: ToJSON a => Snowflake -> a -> ChannelRequest Object
DeleteChannelPermission :: Snowflake -> Snowflake -> ChannelRequest ()
TriggerTypingIndicator :: Snowflake -> ChannelRequest ()
GetPinnedMessages :: Snowflake -> ChannelRequest [Message]
AddPinnedMessage :: Snowflake -> Snowflake -> ChannelRequest ()
DeletePinnedMessage :: Snowflake -> Snowflake -> ChannelRequest ()
instance Hashable (ChannelRequest a) where
hashWithSalt s (GetChannel chan) = hashWithSalt s ("get_chan"::Text, chan)
hashWithSalt s (ModifyChannel chan _) = hashWithSalt s ("mod_chan"::Text, chan)
hashWithSalt s (DeleteChannel chan) = hashWithSalt s ("mod_chan"::Text, chan)
hashWithSalt s (GetChannelMessages chan _) = hashWithSalt s ("msg"::Text, chan)
hashWithSalt s (GetChannelMessage chan _) = hashWithSalt s ("get_msg"::Text, chan)
hashWithSalt s (CreateMessage chan _ _) = hashWithSalt s ("msg"::Text, chan)
hashWithSalt s (UploadFile chan _ _) = hashWithSalt s ("msg"::Text, chan)
hashWithSalt s (EditMessage (Message _ chan _ _ _ _ _ _ _ _ _ _ _ _) _ _) =
hashWithSalt s ("get_msg"::Text, chan)
hashWithSalt s (DeleteMessage (Message _ chan _ _ _ _ _ _ _ _ _ _ _ _)) =
hashWithSalt s ("get_msg"::Text, chan)
hashWithSalt s (BulkDeleteMessage chan _) = hashWithSalt s ("del_msgs"::Text, chan)
hashWithSalt s (EditChannelPermissions chan _ _) = hashWithSalt s ("perms"::Text, chan)
hashWithSalt s (GetChannelInvites chan) = hashWithSalt s ("invites"::Text, chan)
hashWithSalt s (CreateChannelInvite chan _) = hashWithSalt s ("invites"::Text, chan)
hashWithSalt s (DeleteChannelPermission chan _) = hashWithSalt s ("perms"::Text, chan)
hashWithSalt s (TriggerTypingIndicator chan) = hashWithSalt s ("tti"::Text, chan)
hashWithSalt s (GetPinnedMessages chan) = hashWithSalt s ("pins"::Text, chan)
hashWithSalt s (AddPinnedMessage chan _) = hashWithSalt s ("pin"::Text, chan)
hashWithSalt s (DeletePinnedMessage chan _) = hashWithSalt s ("pin"::Text, chan)
instance Eq (ChannelRequest a) where
a == b = hash a == hash b
instance RateLimit (ChannelRequest a) where
getRateLimit req = do
DiscordState {getRateLimits=rl} <- ST.get
now <- ST.liftIO (fmap round getPOSIXTime :: IO Int)
ST.liftIO . atomically $ do
rateLimits <- readTVar rl
case lookup (hash req) rateLimits of
Nothing -> return Nothing
Just a
| a >= now -> return $ Just a
| otherwise -> modifyTVar' rl (Dc.delete $ hash req) >> return Nothing
setRateLimit req reset = do
DiscordState {getRateLimits=rl} <- ST.get
ST.liftIO . atomically . modifyTVar rl $ Dc.insert (hash req) reset
instance (FromJSON a) => DoFetch (ChannelRequest a) where
doFetch req = do
waitRateLimit req
SyncFetched <$> fetch req
fetch :: FromJSON a => ChannelRequest a -> DiscordM a
fetch request = do
req <- baseRequest
(resp, rlRem, rlNext) <- lift $ do
resp <- case request of
GetChannel chan -> getWith req
(baseURL ++ "/channels/" ++ show chan)
ModifyChannel chan patch -> customPayloadMethodWith "PATCH" req
(baseURL ++ "/channels/" ++ show chan)
(toJSON patch)
DeleteChannel chan -> deleteWith req
(baseURL ++ "/channels/" ++ show chan)
GetChannelMessages chan patch -> getWith
(Prelude.foldr (\(k, v) -> param k .~ [v]) req patch)
(baseURL ++ "/channels/" ++ show chan ++ "/messages")
GetChannelMessage chan msg -> getWith req
(baseURL ++ "/channels/" ++ show chan ++ "/messages/" ++ show msg)
CreateMessage chan msg embed -> postWith req
(baseURL ++ "/channels/" ++ show chan ++ "/messages")
(object $ [("content", toJSON msg)] <> maybeEmbed embed)
UploadFile chan msg file -> postWith
(req & header "Content-Type" .~ ["multipart/form-data"])
(baseURL ++ "/channels/" ++ show chan ++ "/messages")
["content" := msg, "file" := file]
EditMessage (Message msg chan _ _ _ _ _ _ _ _ _ _ _ _) new embed ->
customPayloadMethodWith "PATCH" req
(baseURL ++ "/channels/" ++ show chan ++ "/messages/" ++ show msg)
(object $ [("content", toJSON new)] <> maybeEmbed embed)
DeleteMessage (Message msg chan _ _ _ _ _ _ _ _ _ _ _ _) ->
deleteWith req
(baseURL ++ "/channels/" ++ show chan ++ "/messages/" ++ show msg)
BulkDeleteMessage chan msgs -> postWith req
(baseURL ++ "/channels/" ++ show chan ++ "/messages/bulk-delete")
(object
[("messages", toJSON
$ Prelude.map (\(Message msg _ _ _ _ _ _ _ _ _ _ _ _ _) -> msg) msgs)])
EditChannelPermissions chan perm patch -> putWith req
(baseURL ++ "/channels/" ++ show chan ++ "/permissions/" ++ show perm)
(toJSON patch)
GetChannelInvites chan -> getWith req
(baseURL ++ "/channels/" ++ show chan ++ "/invites")
CreateChannelInvite chan patch -> postWith req
(baseURL ++ "/channels/" ++ show chan ++ "/invites")
(toJSON patch)
DeleteChannelPermission chan perm -> deleteWith req
(baseURL ++ "/channels/" ++ show chan ++ "/permissions/" ++ show perm)
TriggerTypingIndicator chan -> postWith req
(baseURL ++ "/channels/" ++ show chan ++ "/typing")
(toJSON ([]::[Int]))
GetPinnedMessages chan -> getWith req
(baseURL ++ "/channels/" ++ show chan ++ "/pins")
AddPinnedMessage chan msg -> putWith req
(baseURL ++ "/channels/" ++ show chan ++ "/pins/" ++ show msg)
(toJSON ([]::[Int]))
DeletePinnedMessage chan msg -> deleteWith req
(baseURL ++ "/channels/" ++ show chan ++ "/pins/" ++ show msg)
return (justRight . eitherDecode $ resp ^. responseBody
, justRight . eitherDecodeStrict $ resp ^. responseHeader "X-RateLimit-Remaining"::Int
, justRight . eitherDecodeStrict $ resp ^. responseHeader "X-RateLimit-Reset"::Int)
when (rlRem == 0) $ setRateLimit request rlNext
return resp
where
maybeEmbed :: Maybe Embed -> [(Text, Value)]
maybeEmbed = maybe [] $ \embed -> [("embed", toJSON embed)]