-- | 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 (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 (execDiscordM (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