discord-hs-0.1.3: src/Network/Discord/Framework.hs
{-# LANGUAGE TypeFamilies, ExistentialQuantification, RankNTypes, MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Provides a convenience framework for writing Discord bots without dealing with Pipes
module Network.Discord.Framework where
import Control.Concurrent
import Control.Monad.Writer
import Data.Proxy
import Control.Concurrent.STM
import Control.Monad.State (execStateT, get)
import Data.Aeson (Object)
import Pipes ((~>))
import Pipes.Core hiding (Proxy)
import System.Log.Logger
import Network.Discord.Gateway as D
import Network.Discord.Rest as D
import Network.Discord.Types as D
-- | Isolated state representation for use with async event handling
asyncState :: D.Client a => a -> Effect DiscordM DiscordState
asyncState client = do
DiscordState { getRateLimits = limits } <- get
return $ DiscordState
Running
client
undefined
undefined
limits
-- | Basic client implementation. Most likely suitable for most bots.
data BotClient = BotClient Auth
instance D.Client BotClient where
getAuth (BotClient auth) = auth
-- | This should be the entrypoint for most Discord bots.
runBot :: Auth -> DiscordBot BotClient () -> IO ()
runBot auth bot = runBotWith (BotClient auth) bot
-- | A variant of 'runBot' which allows the user to specify a custom client implementation.
runBotWith :: D.Client a => a -> DiscordBot a () -> IO ()
runBotWith client bot = do
gateway <- getGateway
atomically $ writeTVar getTMClient client
runWebsocket gateway client $ do
DiscordState {getWebSocket=ws} <- get
(eventCore ~> (handle $ execWriter bot)) ws
-- | Utility function to split event handlers into a seperate thread
runAsync :: D.Client client => Proxy client -> Effect DiscordM () -> Effect DiscordM ()
runAsync c effect = do
client <- liftIO . atomically $ getSTMClient c
st <- asyncState client
liftIO . void $ forkFinally
(execStateT (runEffect effect) st)
finish
where
finish (Right DiscordState{getClient = st}) = atomically $ mergeClient st
finish (Left err) = errorM "Language.Discord.Events" $ show err
-- | Monad to compose event handlers
type DiscordBot c a = Writer (Handle c) a
-- | Event handlers for 'Gateway' events. These correspond to events listed in
-- 'Event'
data D.Client c => Handle c = Null
| Misc (Event -> Effect DiscordM ())
| ReadyEvent (Init -> Effect DiscordM ())
| ResumedEvent (Object -> Effect DiscordM ())
| ChannelCreateEvent (Channel -> Effect DiscordM ())
| ChannelUpdateEvent (Channel -> Effect DiscordM ())
| ChannelDeleteEvent (Channel -> Effect DiscordM ())
| GuildCreateEvent (Guild -> Effect DiscordM ())
| GuildUpdateEvent (Guild -> Effect DiscordM ())
| GuildDeleteEvent (Guild -> Effect DiscordM ())
| GuildBanAddEvent (Member -> Effect DiscordM ())
| GuildBanRemoveEvent (Member -> Effect DiscordM ())
| GuildEmojiUpdateEvent (Object -> Effect DiscordM ())
| GuildIntegrationsUpdateEvent (Object -> Effect DiscordM ())
| GuildMemberAddEvent (Member -> Effect DiscordM ())
| GuildMemberRemoveEvent (Member -> Effect DiscordM ())
| GuildMemberUpdateEvent (Member -> Effect DiscordM ())
| GuildMemberChunkEvent (Object -> Effect DiscordM ())
| GuildRoleCreateEvent (Object -> Effect DiscordM ())
| GuildRoleUpdateEvent (Object -> Effect DiscordM ())
| GuildRoleDeleteEvent (Object -> Effect DiscordM ())
| MessageCreateEvent (Message -> Effect DiscordM ())
| MessageUpdateEvent (Message -> Effect DiscordM ())
| MessageDeleteEvent (Object -> Effect DiscordM ())
| MessageDeleteBulkEvent (Object -> Effect DiscordM ())
| PresenceUpdateEvent (Object -> Effect DiscordM ())
| TypingStartEvent (Object -> Effect DiscordM ())
| UserSettingsUpdateEvent (Object -> Effect DiscordM ())
| UserUpdateEvent (Object -> Effect DiscordM ())
| VoiceStateUpdateEvent (Object -> Effect DiscordM ())
| VoiceServerUpdateEvent (Object -> Effect DiscordM ())
| Event String (Object -> Effect DiscordM ())
-- | Provides a typehint for the correct 'D.Client' given an Event 'Handle'
clientProxy :: Handle c -> Proxy c
clientProxy _ = Proxy
-- | Register an Event 'Handle' in the 'DiscordBot' monad
with :: D.Client c => (a -> Handle c) -> a -> DiscordBot c ()
with f a = tell $ f a
instance D.Client c => Monoid (Handle c) where
mempty = Null
a `mappend` b = Misc (\ev -> handle a ev <> handle b ev)
-- | Asynchronously run an Event 'Handle' against a Gateway 'Event'
handle :: D.Client a => Handle a -> Event -> Effect DiscordM ()
handle a@(Misc p) ev
= runAsync (clientProxy a) $ p ev
handle a@(ReadyEvent p) (D.Ready o)
= runAsync (clientProxy a) $ p o
handle a@(ResumedEvent p) (D.Resumed o)
= runAsync (clientProxy a) $ p o
handle a@(ChannelCreateEvent p) (D.ChannelCreate o)
= runAsync (clientProxy a) $ p o
handle a@(ChannelUpdateEvent p) (D.ChannelUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(ChannelDeleteEvent p) (D.ChannelDelete o)
= runAsync (clientProxy a) $ p o
handle a@(GuildCreateEvent p) (D.GuildCreate o)
= runAsync (clientProxy a) $ p o
handle a@(GuildUpdateEvent p) (D.GuildUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(GuildDeleteEvent p) (D.GuildDelete o)
= runAsync (clientProxy a) $ p o
handle a@(GuildBanAddEvent p) (D.GuildBanAdd o)
= runAsync (clientProxy a) $ p o
handle a@(GuildBanRemoveEvent p) (D.GuildBanRemove o)
= runAsync (clientProxy a) $ p o
handle a@(GuildEmojiUpdateEvent p) (D.GuildEmojiUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(GuildIntegrationsUpdateEvent p) (D.GuildIntegrationsUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(GuildMemberAddEvent p) (D.GuildMemberAdd o)
= runAsync (clientProxy a) $ p o
handle a@(GuildMemberRemoveEvent p) (D.GuildMemberRemove o)
= runAsync (clientProxy a) $ p o
handle a@(GuildMemberUpdateEvent p) (D.GuildMemberUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(GuildMemberChunkEvent p) (D.GuildMemberChunk o)
= runAsync (clientProxy a) $ p o
handle a@(GuildRoleCreateEvent p) (D.GuildRoleCreate o)
= runAsync (clientProxy a) $ p o
handle a@(GuildRoleUpdateEvent p) (D.GuildRoleUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(GuildRoleDeleteEvent p) (D.GuildRoleDelete o)
= runAsync (clientProxy a) $ p o
handle a@(MessageCreateEvent p) (D.MessageCreate o)
= runAsync (clientProxy a) $ p o
handle a@(MessageUpdateEvent p) (D.MessageUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(MessageDeleteEvent p) (D.MessageDelete o)
= runAsync (clientProxy a) $ p o
handle a@(MessageDeleteBulkEvent p) (D.MessageDeleteBulk o)
= runAsync (clientProxy a) $ p o
handle a@(PresenceUpdateEvent p) (D.PresenceUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(TypingStartEvent p) (D.TypingStart o)
= runAsync (clientProxy a) $ p o
handle a@(UserSettingsUpdateEvent p) (D.UserSettingsUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(UserUpdateEvent p) (D.UserUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(VoiceStateUpdateEvent p) (D.VoiceStateUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(VoiceServerUpdateEvent p) (D.VoiceServerUpdate o)
= runAsync (clientProxy a) $ p o
handle a@(Event s p) (D.UnknownEvent v o)
| s == v = runAsync (clientProxy a) $ p o
handle _ ev = liftIO $ debugM "Discord-hs.Language.Events" $ show ev