packages feed

calamity 0.2.0.2 → 0.3.0.0

raw patch · 38 files changed

+2716/−1110 lines, 38 filesdep +randomPVP ok

version bump matches the API change (PVP)

Dependencies added: random

API changes (from Hackage documentation)

- Calamity.HTTP.Channel: [$sel:embed:CreateMessageOptions] :: CreateMessageOptions -> Maybe Embed
- Calamity.HTTP.Channel: [$sel:file:CreateMessageOptions] :: CreateMessageOptions -> Maybe (Text, ByteString)
- Calamity.HTTP.Channel: editMessageEmbed :: Maybe Embed -> EditMessageData
- Calamity.HTTP.Webhook: [$sel:file:ExecuteWebhookOptions] :: ExecuteWebhookOptions -> Maybe ByteString
- Calamity.Types.Model.Channel.Component: [$sel:url:Button] :: Button -> Maybe Text
- Calamity.Types.Model.Channel.Message: [$sel:stickers:Message] :: Message -> Maybe [Object]
- Calamity.Types.Model.Channel.UpdatedMessage: [$sel:stickers:UpdatedMessage] :: UpdatedMessage -> Maybe (MaybeNull [Object])
- Calamity.Types.Model.Guild.Member: [$sel:guildID:Member] :: Member -> Snowflake Guild
- Calamity.Types.Model.Guild.Member: instance Calamity.Types.Snowflake.HasID Calamity.Types.Model.Guild.Guild.Guild Calamity.Types.Model.Guild.Member.Member
- Calamity.Types.Model.Interaction: ApplicationCommandInteractionData :: Snowflake () -> Text -> Maybe ApplicationCommandInteractionDataResolved -> Text -> ComponentType -> ApplicationCommandInteractionData
- Calamity.Types.Model.Interaction: ApplicationCommandInteractionDataResolved :: HashMap (Snowflake User) User -> HashMap (Snowflake Member) Member -> HashMap (Snowflake Role) Role -> HashMap (Snowflake Channel) Channel -> ApplicationCommandInteractionDataResolved
- Calamity.Types.Model.Interaction: [$sel:channels:ApplicationCommandInteractionDataResolved] :: ApplicationCommandInteractionDataResolved -> HashMap (Snowflake Channel) Channel
- Calamity.Types.Model.Interaction: [$sel:componentType:ApplicationCommandInteractionData] :: ApplicationCommandInteractionData -> ComponentType
- Calamity.Types.Model.Interaction: [$sel:customID:ApplicationCommandInteractionData] :: ApplicationCommandInteractionData -> Text
- Calamity.Types.Model.Interaction: [$sel:id:ApplicationCommandInteractionData] :: ApplicationCommandInteractionData -> Snowflake ()
- Calamity.Types.Model.Interaction: [$sel:members:ApplicationCommandInteractionDataResolved] :: ApplicationCommandInteractionDataResolved -> HashMap (Snowflake Member) Member
- Calamity.Types.Model.Interaction: [$sel:name:ApplicationCommandInteractionData] :: ApplicationCommandInteractionData -> Text
- Calamity.Types.Model.Interaction: [$sel:resolved:ApplicationCommandInteractionData] :: ApplicationCommandInteractionData -> Maybe ApplicationCommandInteractionDataResolved
- Calamity.Types.Model.Interaction: [$sel:roles:ApplicationCommandInteractionDataResolved] :: ApplicationCommandInteractionDataResolved -> HashMap (Snowflake Role) Role
- Calamity.Types.Model.Interaction: [$sel:users:ApplicationCommandInteractionDataResolved] :: ApplicationCommandInteractionDataResolved -> HashMap (Snowflake User) User
- Calamity.Types.Model.Interaction: data ApplicationCommandInteractionData
- Calamity.Types.Model.Interaction: data ApplicationCommandInteractionDataResolved
- Calamity.Types.Model.Interaction: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Interaction.ApplicationCommandInteractionData
- Calamity.Types.Model.Interaction: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Interaction.ApplicationCommandInteractionDataResolved
- Calamity.Types.Model.Interaction: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Interaction.ApplicationCommandInteractionDataResolved'
- Calamity.Types.Model.Interaction: instance GHC.Generics.Generic Calamity.Types.Model.Interaction.ApplicationCommandInteractionData
- Calamity.Types.Model.Interaction: instance GHC.Generics.Generic Calamity.Types.Model.Interaction.ApplicationCommandInteractionDataResolved
- Calamity.Types.Model.Interaction: instance GHC.Generics.Generic Calamity.Types.Model.Interaction.ApplicationCommandInteractionDataResolved'
- Calamity.Types.Model.Interaction: instance GHC.Show.Show Calamity.Types.Model.Interaction.ApplicationCommandInteractionData
- Calamity.Types.Model.Interaction: instance GHC.Show.Show Calamity.Types.Model.Interaction.ApplicationCommandInteractionDataResolved
- Calamity.Types.Model.Interaction: instance TextShow.Classes.TextShow Calamity.Types.Model.Interaction.ApplicationCommandInteractionData
- Calamity.Types.Model.Interaction: instance TextShow.Classes.TextShow Calamity.Types.Model.Interaction.ApplicationCommandInteractionDataResolved
- Calamity.Types.Model.Interaction: instance TextShow.Classes.TextShow Calamity.Types.Model.Interaction.ApplicationCommandInteractionDataResolved'
- Calamity.Types.Model.Interaction: instance TextShow.Classes.TextShow Calamity.Types.Model.Interaction.Interaction
- Calamity.Types.Model.Interaction: instance TextShow.Classes.TextShow Calamity.Types.Model.Interaction.InteractionType
- Calamity.Types.Tellable: TFile :: Text -> ByteString -> TFile
- Calamity.Types.Tellable: data TFile
- Calamity.Types.Tellable: instance Calamity.Types.Tellable.ToMessage Calamity.Types.Tellable.TFile
- Calamity.Types.Tellable: instance GHC.Generics.Generic Calamity.Types.Tellable.TFile
- Calamity.Types.Tellable: instance GHC.Show.Show Calamity.Types.Tellable.TFile
+ Calamity.Gateway.Types: INTERACTION_CREATE :: DispatchType
+ Calamity.HTTP: [GetBotToken] :: TokenEff m Token
+ Calamity.HTTP: [GetRatelimitState] :: RatelimitEff m RateLimitState
+ Calamity.HTTP: data RatelimitEff m a
+ Calamity.HTTP: data TokenEff m a
+ Calamity.HTTP.Channel: CreateMessageAttachment :: Text -> Maybe Text -> ByteString -> CreateMessageAttachment
+ Calamity.HTTP.Channel: [$sel:attachments:CreateMessageOptions] :: CreateMessageOptions -> Maybe [CreateMessageAttachment]
+ Calamity.HTTP.Channel: [$sel:components:CreateMessageOptions] :: CreateMessageOptions -> Maybe [Component]
+ Calamity.HTTP.Channel: [$sel:content:CreateMessageAttachment] :: CreateMessageAttachment -> ByteString
+ Calamity.HTTP.Channel: [$sel:description:CreateMessageAttachment] :: CreateMessageAttachment -> Maybe Text
+ Calamity.HTTP.Channel: [$sel:embeds:CreateMessageOptions] :: CreateMessageOptions -> Maybe [Embed]
+ Calamity.HTTP.Channel: [$sel:filename:CreateMessageAttachment] :: CreateMessageAttachment -> Text
+ Calamity.HTTP.Channel: data CreateMessageAttachment
+ Calamity.HTTP.Channel: editMessageComponents :: [Component] -> EditMessageData
+ Calamity.HTTP.Channel: editMessageEmbeds :: [Embed] -> EditMessageData
+ Calamity.HTTP.Channel: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.HTTP.Channel.CreateMessageAttachmentJson
+ Calamity.HTTP.Channel: instance GHC.Generics.Generic Calamity.HTTP.Channel.CreateMessageAttachment
+ Calamity.HTTP.Channel: instance GHC.Generics.Generic Calamity.HTTP.Channel.CreateMessageAttachmentJson
+ Calamity.HTTP.Channel: instance GHC.Show.Show Calamity.HTTP.Channel.CreateMessageAttachment
+ Calamity.HTTP.Channel: instance GHC.Show.Show Calamity.HTTP.Channel.CreateMessageAttachmentJson
+ Calamity.HTTP.Guild: [SearchGuildMembers] :: HasID Guild g => g -> SearchMembersOptions -> GuildRequest [Member]
+ Calamity.HTTP.Guild: instance Data.Default.Class.Default Calamity.HTTP.Guild.GetGuildBansOptions
+ Calamity.HTTP.Guild: instance GHC.Generics.Generic Calamity.HTTP.Guild.GetGuildBansOptions
+ Calamity.HTTP.Guild: instance GHC.Generics.Generic Calamity.HTTP.Guild.SearchMembersOptions
+ Calamity.HTTP.Guild: instance GHC.Show.Show Calamity.HTTP.Guild.GetGuildBansOptions
+ Calamity.HTTP.Guild: instance GHC.Show.Show Calamity.HTTP.Guild.SearchMembersOptions
+ Calamity.HTTP.Interaction: InteractionCallbackAutocomplete :: [InteractionCallbackAutocompleteChoice] -> InteractionCallbackAutocomplete
+ Calamity.HTTP.Interaction: InteractionCallbackAutocompleteChoice :: Text -> HashMap Text Text -> Value -> InteractionCallbackAutocompleteChoice
+ Calamity.HTTP.Interaction: InteractionCallbackMessageOptions :: Maybe Bool -> Maybe Text -> Maybe [Embed] -> Maybe AllowedMentions -> Maybe Bool -> Maybe Bool -> Maybe [Component] -> Maybe [CreateMessageAttachment] -> InteractionCallbackMessageOptions
+ Calamity.HTTP.Interaction: InteractionCallbackModal :: CustomID -> Text -> [Component] -> InteractionCallbackModal
+ Calamity.HTTP.Interaction: [$sel:allowedMentions:InteractionCallbackMessageOptions] :: InteractionCallbackMessageOptions -> Maybe AllowedMentions
+ Calamity.HTTP.Interaction: [$sel:attachments:InteractionCallbackMessageOptions] :: InteractionCallbackMessageOptions -> Maybe [CreateMessageAttachment]
+ Calamity.HTTP.Interaction: [$sel:choices:InteractionCallbackAutocomplete] :: InteractionCallbackAutocomplete -> [InteractionCallbackAutocompleteChoice]
+ Calamity.HTTP.Interaction: [$sel:components:InteractionCallbackMessageOptions] :: InteractionCallbackMessageOptions -> Maybe [Component]
+ Calamity.HTTP.Interaction: [$sel:components:InteractionCallbackModal] :: InteractionCallbackModal -> [Component]
+ Calamity.HTTP.Interaction: [$sel:content:InteractionCallbackMessageOptions] :: InteractionCallbackMessageOptions -> Maybe Text
+ Calamity.HTTP.Interaction: [$sel:customID:InteractionCallbackModal] :: InteractionCallbackModal -> CustomID
+ Calamity.HTTP.Interaction: [$sel:embeds:InteractionCallbackMessageOptions] :: InteractionCallbackMessageOptions -> Maybe [Embed]
+ Calamity.HTTP.Interaction: [$sel:ephemeral:InteractionCallbackMessageOptions] :: InteractionCallbackMessageOptions -> Maybe Bool
+ Calamity.HTTP.Interaction: [$sel:name:InteractionCallbackAutocompleteChoice] :: InteractionCallbackAutocompleteChoice -> Text
+ Calamity.HTTP.Interaction: [$sel:nameLocalizations:InteractionCallbackAutocompleteChoice] :: InteractionCallbackAutocompleteChoice -> HashMap Text Text
+ Calamity.HTTP.Interaction: [$sel:suppressEmbeds:InteractionCallbackMessageOptions] :: InteractionCallbackMessageOptions -> Maybe Bool
+ Calamity.HTTP.Interaction: [$sel:title:InteractionCallbackModal] :: InteractionCallbackModal -> Text
+ Calamity.HTTP.Interaction: [$sel:tts:InteractionCallbackMessageOptions] :: InteractionCallbackMessageOptions -> Maybe Bool
+ Calamity.HTTP.Interaction: [$sel:value:InteractionCallbackAutocompleteChoice] :: InteractionCallbackAutocompleteChoice -> Value
+ Calamity.HTTP.Interaction: [CreateFollowupMessage] :: HasID Application i => i -> InteractionToken -> InteractionCallbackMessageOptions -> InteractionRequest ()
+ Calamity.HTTP.Interaction: [CreateResponseAutocomplete] :: HasID Interaction i => i -> InteractionToken -> InteractionCallbackAutocomplete -> InteractionRequest ()
+ Calamity.HTTP.Interaction: [CreateResponseDeferComponent] :: HasID Interaction i => i -> InteractionToken -> InteractionRequest ()
+ Calamity.HTTP.Interaction: [CreateResponseDefer] :: HasID Interaction i => i -> InteractionToken -> Bool -> InteractionRequest ()
+ Calamity.HTTP.Interaction: [CreateResponseMessage] :: HasID Interaction i => i -> InteractionToken -> InteractionCallbackMessageOptions -> InteractionRequest ()
+ Calamity.HTTP.Interaction: [CreateResponseModal] :: HasID Interaction i => i -> InteractionToken -> InteractionCallbackModal -> InteractionRequest ()
+ Calamity.HTTP.Interaction: [CreateResponseUpdate] :: HasID Interaction i => i -> InteractionToken -> InteractionCallbackMessageOptions -> InteractionRequest ()
+ Calamity.HTTP.Interaction: [DeleteFollowupMessage] :: (HasID Application i, HasID Message m) => i -> m -> InteractionToken -> InteractionRequest ()
+ Calamity.HTTP.Interaction: [DeleteOriginalInteractionResponse] :: HasID Application i => i -> InteractionToken -> InteractionRequest ()
+ Calamity.HTTP.Interaction: [EditFollowupMessage] :: (HasID Application i, HasID Message m) => i -> m -> InteractionToken -> InteractionCallbackMessageOptions -> InteractionRequest ()
+ Calamity.HTTP.Interaction: [EditOriginalInteractionResponse] :: HasID Application i => i -> InteractionToken -> InteractionCallbackMessageOptions -> InteractionRequest Message
+ Calamity.HTTP.Interaction: [GetFollowupMessage] :: (HasID Application i, HasID Message m) => i -> m -> InteractionToken -> InteractionRequest Message
+ Calamity.HTTP.Interaction: [GetOriginalInteractionResponse] :: HasID Application i => i -> InteractionToken -> InteractionRequest Message
+ Calamity.HTTP.Interaction: data InteractionCallbackAutocompleteChoice
+ Calamity.HTTP.Interaction: data InteractionCallbackMessageOptions
+ Calamity.HTTP.Interaction: data InteractionCallbackModal
+ Calamity.HTTP.Interaction: data InteractionRequest a
+ Calamity.HTTP.Interaction: instance Calamity.HTTP.Internal.Request.Request (Calamity.HTTP.Interaction.InteractionRequest a)
+ Calamity.HTTP.Interaction: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.HTTP.Interaction.CreateMessageAttachmentJson
+ Calamity.HTTP.Interaction: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.HTTP.Interaction.CreateResponseMessageJson
+ Calamity.HTTP.Interaction: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.HTTP.Interaction.InteractionCallback
+ Calamity.HTTP.Interaction: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.HTTP.Interaction.InteractionCallbackAutocomplete
+ Calamity.HTTP.Interaction: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.HTTP.Interaction.InteractionCallbackAutocompleteChoice
+ Calamity.HTTP.Interaction: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.HTTP.Interaction.InteractionCallbackModal
+ Calamity.HTTP.Interaction: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.HTTP.Interaction.InteractionCallbackType
+ Calamity.HTTP.Interaction: instance Data.Default.Class.Default Calamity.HTTP.Interaction.InteractionCallbackMessageOptions
+ Calamity.HTTP.Interaction: instance GHC.Generics.Generic Calamity.HTTP.Interaction.CreateMessageAttachmentJson
+ Calamity.HTTP.Interaction: instance GHC.Generics.Generic Calamity.HTTP.Interaction.CreateResponseMessageJson
+ Calamity.HTTP.Interaction: instance GHC.Generics.Generic Calamity.HTTP.Interaction.InteractionCallback
+ Calamity.HTTP.Interaction: instance GHC.Generics.Generic Calamity.HTTP.Interaction.InteractionCallbackAutocomplete
+ Calamity.HTTP.Interaction: instance GHC.Generics.Generic Calamity.HTTP.Interaction.InteractionCallbackAutocompleteChoice
+ Calamity.HTTP.Interaction: instance GHC.Generics.Generic Calamity.HTTP.Interaction.InteractionCallbackMessageOptions
+ Calamity.HTTP.Interaction: instance GHC.Generics.Generic Calamity.HTTP.Interaction.InteractionCallbackModal
+ Calamity.HTTP.Interaction: instance GHC.Generics.Generic Calamity.HTTP.Interaction.InteractionCallbackType
+ Calamity.HTTP.Interaction: instance GHC.Show.Show Calamity.HTTP.Interaction.CreateMessageAttachmentJson
+ Calamity.HTTP.Interaction: instance GHC.Show.Show Calamity.HTTP.Interaction.CreateResponseMessageJson
+ Calamity.HTTP.Interaction: instance GHC.Show.Show Calamity.HTTP.Interaction.InteractionCallback
+ Calamity.HTTP.Interaction: instance GHC.Show.Show Calamity.HTTP.Interaction.InteractionCallbackAutocomplete
+ Calamity.HTTP.Interaction: instance GHC.Show.Show Calamity.HTTP.Interaction.InteractionCallbackAutocompleteChoice
+ Calamity.HTTP.Interaction: instance GHC.Show.Show Calamity.HTTP.Interaction.InteractionCallbackMessageOptions
+ Calamity.HTTP.Interaction: instance GHC.Show.Show Calamity.HTTP.Interaction.InteractionCallbackModal
+ Calamity.HTTP.Interaction: instance GHC.Show.Show Calamity.HTTP.Interaction.InteractionCallbackType
+ Calamity.HTTP.Interaction: instance TextShow.Classes.TextShow Calamity.HTTP.Interaction.InteractionCallbackType
+ Calamity.HTTP.Interaction: newtype InteractionCallbackAutocomplete
+ Calamity.HTTP.Internal.Ratelimit: [GetRatelimitState] :: RatelimitEff m RateLimitState
+ Calamity.HTTP.Internal.Ratelimit: data RatelimitEff m a
+ Calamity.HTTP.Internal.Ratelimit: getRatelimitState :: forall r_ae7YB. Member RatelimitEff r_ae7YB => Sem r_ae7YB RateLimitState
+ Calamity.HTTP.Webhook: [$sel:allowedMentions:ExecuteWebhookOptions] :: ExecuteWebhookOptions -> Maybe AllowedMentions
+ Calamity.HTTP.Webhook: [$sel:attachments:ExecuteWebhookOptions] :: ExecuteWebhookOptions -> [CreateMessageAttachment]
+ Calamity.HTTP.Webhook: [$sel:components:ExecuteWebhookOptions] :: ExecuteWebhookOptions -> [Component]
+ Calamity.HTTP.Webhook: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.HTTP.Webhook.CreateMessageAttachmentJson
+ Calamity.HTTP.Webhook: instance GHC.Generics.Generic Calamity.HTTP.Webhook.CreateMessageAttachmentJson
+ Calamity.HTTP.Webhook: instance GHC.Show.Show Calamity.HTTP.Webhook.CreateMessageAttachmentJson
+ Calamity.Interactions.Eff: [GetInteraction] :: InteractionEff m Interaction
+ Calamity.Interactions.Eff: data InteractionEff m a
+ Calamity.Interactions.Eff: getApplicationID :: Member InteractionEff r => Sem r (Snowflake Application)
+ Calamity.Interactions.Eff: getInteraction :: forall r_acos6. Member InteractionEff r_acos6 => Sem r_acos6 Interaction
+ Calamity.Interactions.Eff: getInteractionID :: Member InteractionEff r => Sem r (Snowflake Interaction)
+ Calamity.Interactions.Eff: getInteractionToken :: Member InteractionEff r => Sem r InteractionToken
+ Calamity.Interactions.Eff: getInteractionUser :: Member InteractionEff r => Sem r (Snowflake User)
+ Calamity.Interactions.Utils: defer :: Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, Embed IO] r => Sem r (Either RestError ())
+ Calamity.Interactions.Utils: deferComponent :: Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, Embed IO] r => Sem r (Either RestError ())
+ Calamity.Interactions.Utils: deferEphemeral :: Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, Embed IO] r => Sem r (Either RestError ())
+ Calamity.Interactions.Utils: edit :: forall t r. (Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, Embed IO] r, ToMessage t) => t -> Sem r (Either RestError ())
+ Calamity.Interactions.Utils: followUp :: forall t r. (Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, Embed IO] r, ToMessage t) => t -> Sem r (Either RestError ())
+ Calamity.Interactions.Utils: followUpEphemeral :: forall t r. (Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, Embed IO] r, ToMessage t) => t -> Sem r (Either RestError ())
+ Calamity.Interactions.Utils: pushModal :: Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, Embed IO] r => Text -> [Component] -> Sem r (Either RestError ())
+ Calamity.Interactions.Utils: respond :: forall t r. (Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, Embed IO] r, ToMessage t) => t -> Sem r (Either RestError ())
+ Calamity.Interactions.Utils: respondEphemeral :: forall t r. (Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, Embed IO] r, ToMessage t) => t -> Sem r (Either RestError ())
+ Calamity.Interactions.Utils: userLocalState :: forall r s a. Member InteractionEff r => s -> Sem (State s : r) a -> Sem r a
+ Calamity.Interactions.View: [EndView] :: ret -> ViewEff ret inp sendResp m ()
+ Calamity.Interactions.View: [GetSendResponse] :: ViewEff ret inp sendResp m sendResp
+ Calamity.Interactions.View: [ReplaceView] :: View inp -> ([Component] -> m ()) -> ViewEff ret inp sendResp m ()
+ Calamity.Interactions.View: button :: ButtonStyle -> Text -> View Bool
+ Calamity.Interactions.View: button' :: (Button -> Button) -> View Bool
+ Calamity.Interactions.View: data View a
+ Calamity.Interactions.View: data ViewEff ret inp sendResp m a
+ Calamity.Interactions.View: deleteInitialMsg :: (BotC r, Member (ViewEff a inp (Either e Message)) r) => Sem r ()
+ Calamity.Interactions.View: endView :: forall ret_ai9f5 inp_ai9f6 sendResp_ai9f7 r_ai9tn. Member (ViewEff ret_ai9f5 inp_ai9f6 sendResp_ai9f7) r_ai9tn => ret_ai9f5 -> Sem r_ai9tn ()
+ Calamity.Interactions.View: getSendResponse :: forall ret_ai9fd inp_ai9fe sendResp_ai9ff r_ai9ts. Member (ViewEff ret_ai9fd inp_ai9fe sendResp_ai9ff) r_ai9ts => Sem r_ai9ts sendResp_ai9ff
+ Calamity.Interactions.View: instance (TypeError ...) => GHC.Base.Monad Calamity.Interactions.View.View
+ Calamity.Interactions.View: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Interactions.View.TextInputDecoded
+ Calamity.Interactions.View: instance GHC.Base.Applicative Calamity.Interactions.View.ExtractResult
+ Calamity.Interactions.View: instance GHC.Base.Applicative Calamity.Interactions.View.View
+ Calamity.Interactions.View: instance GHC.Base.Functor Calamity.Interactions.View.ExtractResult
+ Calamity.Interactions.View: instance GHC.Base.Functor Calamity.Interactions.View.View
+ Calamity.Interactions.View: instance GHC.Base.Functor Calamity.Interactions.View.ViewComponent
+ Calamity.Interactions.View: instance GHC.Base.Semigroup Calamity.Interactions.View.ExtractOkType
+ Calamity.Interactions.View: instance GHC.Generics.Generic Calamity.Interactions.View.TextInputDecoded
+ Calamity.Interactions.View: instance GHC.Show.Show Calamity.Interactions.View.ExtractOkType
+ Calamity.Interactions.View: instance GHC.Show.Show Calamity.Interactions.View.TextInputDecoded
+ Calamity.Interactions.View: instance GHC.Show.Show a => GHC.Show.Show (Calamity.Interactions.View.ExtractResult a)
+ Calamity.Interactions.View: instantiateView :: RandomGen g => g -> View a -> (ViewInstance a, g)
+ Calamity.Interactions.View: replaceView :: forall ret_ai9fb inp_ai9f9 sendResp_ai9fc r_ai9tp. Member (ViewEff ret_ai9fb inp_ai9f9 sendResp_ai9fc) r_ai9tp => View inp_ai9f9 -> ([Component] -> Sem r_ai9tp ()) -> Sem r_ai9tp ()
+ Calamity.Interactions.View: row :: View a -> View a
+ Calamity.Interactions.View: runView :: BotC r => View inp -> ([Component] -> Sem r sendResp) -> (inp -> Sem (InteractionEff : (ViewEff a inp sendResp : r)) ()) -> Sem r a
+ Calamity.Interactions.View: runViewInstance :: BotC r => sendResp -> ViewInstance inp -> (inp -> Sem (InteractionEff : (ViewEff a inp sendResp : r)) ()) -> Sem r a
+ Calamity.Interactions.View: select :: [Text] -> View (Maybe Text)
+ Calamity.Interactions.View: select' :: [SelectOption] -> (Select -> Select) -> View (Maybe [Text])
+ Calamity.Interactions.View: textInput :: TextInputStyle -> Text -> View Text
+ Calamity.Interactions.View: textInput' :: TextInputStyle -> Text -> (TextInput -> TextInput) -> View (Maybe Text)
+ Calamity.Types.Model.Channel.Component: CustomID :: Text -> CustomID
+ Calamity.Types.Model.Channel.Component: LinkButton :: ButtonStyle -> Maybe Text -> Maybe RawEmoji -> Text -> Bool -> LinkButton
+ Calamity.Types.Model.Channel.Component: LinkButton' :: LinkButton -> Component
+ Calamity.Types.Model.Channel.Component: Select :: [SelectOption] -> Maybe Text -> Maybe Int -> Maybe Int -> Bool -> CustomID -> Select
+ Calamity.Types.Model.Channel.Component: Select' :: Select -> Component
+ Calamity.Types.Model.Channel.Component: SelectOption :: Text -> Text -> Maybe Text -> Maybe RawEmoji -> Bool -> SelectOption
+ Calamity.Types.Model.Channel.Component: SelectType :: ComponentType
+ Calamity.Types.Model.Channel.Component: TextInput :: TextInputStyle -> Text -> Maybe Int -> Maybe Int -> Bool -> Maybe Text -> Maybe Text -> CustomID -> TextInput
+ Calamity.Types.Model.Channel.Component: TextInput' :: TextInput -> Component
+ Calamity.Types.Model.Channel.Component: TextInputParagraph :: TextInputStyle
+ Calamity.Types.Model.Channel.Component: TextInputShort :: TextInputStyle
+ Calamity.Types.Model.Channel.Component: TextInputType :: ComponentType
+ Calamity.Types.Model.Channel.Component: [$sel:customID:Select] :: Select -> CustomID
+ Calamity.Types.Model.Channel.Component: [$sel:customID:TextInput] :: TextInput -> CustomID
+ Calamity.Types.Model.Channel.Component: [$sel:default_:SelectOption] :: SelectOption -> Bool
+ Calamity.Types.Model.Channel.Component: [$sel:description:SelectOption] :: SelectOption -> Maybe Text
+ Calamity.Types.Model.Channel.Component: [$sel:disabled:LinkButton] :: LinkButton -> Bool
+ Calamity.Types.Model.Channel.Component: [$sel:disabled:Select] :: Select -> Bool
+ Calamity.Types.Model.Channel.Component: [$sel:emoji:LinkButton] :: LinkButton -> Maybe RawEmoji
+ Calamity.Types.Model.Channel.Component: [$sel:emoji:SelectOption] :: SelectOption -> Maybe RawEmoji
+ Calamity.Types.Model.Channel.Component: [$sel:label:LinkButton] :: LinkButton -> Maybe Text
+ Calamity.Types.Model.Channel.Component: [$sel:label:SelectOption] :: SelectOption -> Text
+ Calamity.Types.Model.Channel.Component: [$sel:label:TextInput] :: TextInput -> Text
+ Calamity.Types.Model.Channel.Component: [$sel:maxLength:TextInput] :: TextInput -> Maybe Int
+ Calamity.Types.Model.Channel.Component: [$sel:maxValues:Select] :: Select -> Maybe Int
+ Calamity.Types.Model.Channel.Component: [$sel:minLength:TextInput] :: TextInput -> Maybe Int
+ Calamity.Types.Model.Channel.Component: [$sel:minValues:Select] :: Select -> Maybe Int
+ Calamity.Types.Model.Channel.Component: [$sel:options:Select] :: Select -> [SelectOption]
+ Calamity.Types.Model.Channel.Component: [$sel:placeholder:Select] :: Select -> Maybe Text
+ Calamity.Types.Model.Channel.Component: [$sel:placeholder:TextInput] :: TextInput -> Maybe Text
+ Calamity.Types.Model.Channel.Component: [$sel:required:TextInput] :: TextInput -> Bool
+ Calamity.Types.Model.Channel.Component: [$sel:style:TextInput] :: TextInput -> TextInputStyle
+ Calamity.Types.Model.Channel.Component: [$sel:stype:LinkButton] :: LinkButton -> ButtonStyle
+ Calamity.Types.Model.Channel.Component: [$sel:url:LinkButton] :: LinkButton -> Text
+ Calamity.Types.Model.Channel.Component: [$sel:value:SelectOption] :: SelectOption -> Text
+ Calamity.Types.Model.Channel.Component: [$sel:value:TextInput] :: TextInput -> Maybe Text
+ Calamity.Types.Model.Channel.Component: data LinkButton
+ Calamity.Types.Model.Channel.Component: data Select
+ Calamity.Types.Model.Channel.Component: data SelectOption
+ Calamity.Types.Model.Channel.Component: data TextInput
+ Calamity.Types.Model.Channel.Component: data TextInputStyle
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Channel.Component.CustomID
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Channel.Component.LinkButton
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Channel.Component.Select
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Channel.Component.SelectOption
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Channel.Component.TextInput
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Channel.Component.TextInputStyle
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.Types.Model.Channel.Component.CustomID
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.Types.Model.Channel.Component.LinkButton
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.Types.Model.Channel.Component.Select
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.Types.Model.Channel.Component.SelectOption
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.Types.Model.Channel.Component.TextInput
+ Calamity.Types.Model.Channel.Component: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.Types.Model.Channel.Component.TextInputStyle
+ Calamity.Types.Model.Channel.Component: instance GHC.Classes.Eq Calamity.Types.Model.Channel.Component.ButtonStyle
+ Calamity.Types.Model.Channel.Component: instance GHC.Classes.Eq Calamity.Types.Model.Channel.Component.ComponentType
+ Calamity.Types.Model.Channel.Component: instance GHC.Classes.Eq Calamity.Types.Model.Channel.Component.CustomID
+ Calamity.Types.Model.Channel.Component: instance GHC.Classes.Ord Calamity.Types.Model.Channel.Component.CustomID
+ Calamity.Types.Model.Channel.Component: instance GHC.Generics.Generic Calamity.Types.Model.Channel.Component.CustomID
+ Calamity.Types.Model.Channel.Component: instance GHC.Generics.Generic Calamity.Types.Model.Channel.Component.LinkButton
+ Calamity.Types.Model.Channel.Component: instance GHC.Generics.Generic Calamity.Types.Model.Channel.Component.Select
+ Calamity.Types.Model.Channel.Component: instance GHC.Generics.Generic Calamity.Types.Model.Channel.Component.SelectOption
+ Calamity.Types.Model.Channel.Component: instance GHC.Generics.Generic Calamity.Types.Model.Channel.Component.TextInput
+ Calamity.Types.Model.Channel.Component: instance GHC.Generics.Generic Calamity.Types.Model.Channel.Component.TextInputStyle
+ Calamity.Types.Model.Channel.Component: instance GHC.Show.Show Calamity.Types.Model.Channel.Component.CustomID
+ Calamity.Types.Model.Channel.Component: instance GHC.Show.Show Calamity.Types.Model.Channel.Component.LinkButton
+ Calamity.Types.Model.Channel.Component: instance GHC.Show.Show Calamity.Types.Model.Channel.Component.Select
+ Calamity.Types.Model.Channel.Component: instance GHC.Show.Show Calamity.Types.Model.Channel.Component.SelectOption
+ Calamity.Types.Model.Channel.Component: instance GHC.Show.Show Calamity.Types.Model.Channel.Component.TextInput
+ Calamity.Types.Model.Channel.Component: instance GHC.Show.Show Calamity.Types.Model.Channel.Component.TextInputStyle
+ Calamity.Types.Model.Channel.Component: instance System.Random.Internal.Uniform Calamity.Types.Model.Channel.Component.CustomID
+ Calamity.Types.Model.Channel.Component: instance TextShow.Classes.TextShow Calamity.Types.Model.Channel.Component.CustomID
+ Calamity.Types.Model.Channel.Component: instance TextShow.Classes.TextShow Calamity.Types.Model.Channel.Component.LinkButton
+ Calamity.Types.Model.Channel.Component: instance TextShow.Classes.TextShow Calamity.Types.Model.Channel.Component.Select
+ Calamity.Types.Model.Channel.Component: instance TextShow.Classes.TextShow Calamity.Types.Model.Channel.Component.SelectOption
+ Calamity.Types.Model.Channel.Component: instance TextShow.Classes.TextShow Calamity.Types.Model.Channel.Component.TextInput
+ Calamity.Types.Model.Channel.Component: instance TextShow.Classes.TextShow Calamity.Types.Model.Channel.Component.TextInputStyle
+ Calamity.Types.Model.Channel.Component: lbutton :: ButtonStyle -> Text -> LinkButton
+ Calamity.Types.Model.Channel.Component: lbutton' :: ButtonStyle -> Text -> Text -> LinkButton
+ Calamity.Types.Model.Channel.Component: newtype CustomID
+ Calamity.Types.Model.Channel.Component: select :: [SelectOption] -> CustomID -> Select
+ Calamity.Types.Model.Channel.Component: sopt :: Text -> Text -> SelectOption
+ Calamity.Types.Model.Channel.Component: textInput :: TextInputStyle -> Text -> CustomID -> TextInput
+ Calamity.Types.Model.Channel.Message: data family Partial t
+ Calamity.Types.Model.Channel.Message: instance Calamity.Types.Snowflake.HasID Calamity.Types.Model.Channel.Channel (Calamity.Types.Partial.Partial Calamity.Types.Model.Channel.Message.Message)
+ Calamity.Types.Model.Channel.Message: instance Data.Aeson.Types.FromJSON.FromJSON (Calamity.Types.Partial.Partial Calamity.Types.Model.Channel.Message.Message)
+ Calamity.Types.Model.Channel.Message: instance GHC.Generics.Generic (Calamity.Types.Partial.Partial Calamity.Types.Model.Channel.Message.Message)
+ Calamity.Types.Model.Channel.Message: instance GHC.Show.Show (Calamity.Types.Partial.Partial Calamity.Types.Model.Channel.Message.Message)
+ Calamity.Types.Model.Channel.Message: instance TextShow.Classes.TextShow (Calamity.Types.Partial.Partial Calamity.Types.Model.Channel.Message.Message)
+ Calamity.Types.Model.Interaction: ApplicationCommandAutoCompleteType :: InteractionType
+ Calamity.Types.Model.Interaction: InteractionData :: Maybe (Snowflake ApplicationCommand) -> Maybe Text -> Maybe ResolvedInteractionData -> Maybe CustomID -> Maybe ComponentType -> Maybe [Text] -> Maybe (Snowflake ()) -> Maybe [Value] -> InteractionData
+ Calamity.Types.Model.Interaction: InteractionToken :: Text -> InteractionToken
+ Calamity.Types.Model.Interaction: ModalSubmitType :: InteractionType
+ Calamity.Types.Model.Interaction: ResolvedInteractionData :: HashMap (Snowflake User) User -> HashMap (Snowflake Member) Member -> HashMap (Snowflake Role) Role -> HashMap (Snowflake Channel) (Partial Channel) -> HashMap (Snowflake Message) (Partial Message) -> HashMap (Snowflake Attachment) Attachment -> ResolvedInteractionData
+ Calamity.Types.Model.Interaction: [$sel:attachments:ResolvedInteractionData] :: ResolvedInteractionData -> HashMap (Snowflake Attachment) Attachment
+ Calamity.Types.Model.Interaction: [$sel:channels:ResolvedInteractionData] :: ResolvedInteractionData -> HashMap (Snowflake Channel) (Partial Channel)
+ Calamity.Types.Model.Interaction: [$sel:componentType:InteractionData] :: InteractionData -> Maybe ComponentType
+ Calamity.Types.Model.Interaction: [$sel:components:InteractionData] :: InteractionData -> Maybe [Value]
+ Calamity.Types.Model.Interaction: [$sel:customID:InteractionData] :: InteractionData -> Maybe CustomID
+ Calamity.Types.Model.Interaction: [$sel:fromInteractionToken:InteractionToken] :: InteractionToken -> Text
+ Calamity.Types.Model.Interaction: [$sel:guildLocale:Interaction] :: Interaction -> Maybe Text
+ Calamity.Types.Model.Interaction: [$sel:id:InteractionData] :: InteractionData -> Maybe (Snowflake ApplicationCommand)
+ Calamity.Types.Model.Interaction: [$sel:locale:Interaction] :: Interaction -> Maybe Text
+ Calamity.Types.Model.Interaction: [$sel:members:ResolvedInteractionData] :: ResolvedInteractionData -> HashMap (Snowflake Member) Member
+ Calamity.Types.Model.Interaction: [$sel:messages:ResolvedInteractionData] :: ResolvedInteractionData -> HashMap (Snowflake Message) (Partial Message)
+ Calamity.Types.Model.Interaction: [$sel:name:InteractionData] :: InteractionData -> Maybe Text
+ Calamity.Types.Model.Interaction: [$sel:resolved:InteractionData] :: InteractionData -> Maybe ResolvedInteractionData
+ Calamity.Types.Model.Interaction: [$sel:roles:ResolvedInteractionData] :: ResolvedInteractionData -> HashMap (Snowflake Role) Role
+ Calamity.Types.Model.Interaction: [$sel:targetID:InteractionData] :: InteractionData -> Maybe (Snowflake ())
+ Calamity.Types.Model.Interaction: [$sel:users:ResolvedInteractionData] :: ResolvedInteractionData -> HashMap (Snowflake User) User
+ Calamity.Types.Model.Interaction: [$sel:values:InteractionData] :: InteractionData -> Maybe [Text]
+ Calamity.Types.Model.Interaction: data Application
+ Calamity.Types.Model.Interaction: data ApplicationCommand
+ Calamity.Types.Model.Interaction: data InteractionData
+ Calamity.Types.Model.Interaction: data ResolvedInteractionData
+ Calamity.Types.Model.Interaction: instance Calamity.Types.Snowflake.HasID Calamity.Types.Model.Interaction.Application Calamity.Types.Model.Interaction.Interaction
+ Calamity.Types.Model.Interaction: instance Calamity.Types.Snowflake.HasID Calamity.Types.Model.Interaction.Interaction Calamity.Types.Model.Interaction.Interaction
+ Calamity.Types.Model.Interaction: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Interaction.InteractionData
+ Calamity.Types.Model.Interaction: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Interaction.InteractionToken
+ Calamity.Types.Model.Interaction: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Interaction.ResolvedInteractionData
+ Calamity.Types.Model.Interaction: instance Data.Aeson.Types.FromJSON.FromJSON Calamity.Types.Model.Interaction.ResolvedInteractionData'
+ Calamity.Types.Model.Interaction: instance Data.Aeson.Types.ToJSON.ToJSON Calamity.Types.Model.Interaction.InteractionToken
+ Calamity.Types.Model.Interaction: instance GHC.Classes.Eq Calamity.Types.Model.Interaction.InteractionType
+ Calamity.Types.Model.Interaction: instance GHC.Generics.Generic Calamity.Types.Model.Interaction.InteractionData
+ Calamity.Types.Model.Interaction: instance GHC.Generics.Generic Calamity.Types.Model.Interaction.InteractionToken
+ Calamity.Types.Model.Interaction: instance GHC.Generics.Generic Calamity.Types.Model.Interaction.ResolvedInteractionData
+ Calamity.Types.Model.Interaction: instance GHC.Generics.Generic Calamity.Types.Model.Interaction.ResolvedInteractionData'
+ Calamity.Types.Model.Interaction: instance GHC.Show.Show Calamity.Types.Model.Interaction.InteractionData
+ Calamity.Types.Model.Interaction: instance GHC.Show.Show Calamity.Types.Model.Interaction.InteractionToken
+ Calamity.Types.Model.Interaction: instance GHC.Show.Show Calamity.Types.Model.Interaction.ResolvedInteractionData
+ Calamity.Types.Model.Interaction: newtype InteractionToken
+ Calamity.Types.Tellable: instance Calamity.Types.Tellable.ToMessage Calamity.HTTP.Channel.CreateMessageAttachment
+ Calamity.Types.Tellable: instance Calamity.Types.Tellable.ToMessage Calamity.Types.Model.Channel.Component.Button
+ Calamity.Types.Tellable: instance Calamity.Types.Tellable.ToMessage Calamity.Types.Model.Channel.Component.Component
+ Calamity.Types.Tellable: instance Calamity.Types.Tellable.ToMessage Calamity.Types.Model.Channel.Component.LinkButton
+ Calamity.Types.Tellable: instance Calamity.Types.Tellable.ToMessage Calamity.Types.Model.Channel.Component.Select
+ Calamity.Types.Tellable: instance Calamity.Types.Tellable.ToMessage [Calamity.Types.Model.Channel.Component.Button]
+ Calamity.Types.Tellable: instance Calamity.Types.Tellable.ToMessage [Calamity.Types.Model.Channel.Component.Component]
+ Calamity.Types.Tellable: instance Calamity.Types.Tellable.ToMessage [Calamity.Types.Model.Channel.Component.LinkButton]
+ Calamity.Types.Tellable: instance Calamity.Types.Tellable.ToMessage [Calamity.Types.Model.Channel.Component.Select]
+ Calamity.Types.Tellable: runToMessage :: ToMessage a => a -> CreateMessageOptions
+ Calamity.Types.TokenEff: [GetBotToken] :: TokenEff m Token
+ Calamity.Types.TokenEff: data TokenEff m a
+ Calamity.Types.TokenEff: getBotToken :: forall r_ae7UD. Member TokenEff r_ae7UD => Sem r_ae7UD Token
- Calamity.Cache.Eff: delDM :: forall r_acZP7. Member CacheEff r_acZP7 => Snowflake DMChannel -> Sem r_acZP7 ()
+ Calamity.Cache.Eff: delDM :: forall r_adJ5H. Member CacheEff r_adJ5H => Snowflake DMChannel -> Sem r_adJ5H ()
- Calamity.Cache.Eff: delGuild :: forall r_acZP0. Member CacheEff r_acZP0 => Snowflake Guild -> Sem r_acZP0 ()
+ Calamity.Cache.Eff: delGuild :: forall r_adJ5A. Member CacheEff r_adJ5A => Snowflake Guild -> Sem r_adJ5A ()
- Calamity.Cache.Eff: delMessage :: forall r_acZPs. Member CacheEff r_acZPs => Snowflake Message -> Sem r_acZPs ()
+ Calamity.Cache.Eff: delMessage :: forall r_adJ62. Member CacheEff r_adJ62 => Snowflake Message -> Sem r_adJ62 ()
- Calamity.Cache.Eff: delUnavailableGuild :: forall r_acZPl. Member CacheEff r_acZPl => Snowflake Guild -> Sem r_acZPl ()
+ Calamity.Cache.Eff: delUnavailableGuild :: forall r_adJ5V. Member CacheEff r_adJ5V => Snowflake Guild -> Sem r_adJ5V ()
- Calamity.Cache.Eff: delUser :: forall r_acZPe. Member CacheEff r_acZPe => Snowflake User -> Sem r_acZPe ()
+ Calamity.Cache.Eff: delUser :: forall r_adJ5O. Member CacheEff r_adJ5O => Snowflake User -> Sem r_adJ5O ()
- Calamity.Cache.Eff: getBotUser :: forall r_acZOS. Member CacheEff r_acZOS => Sem r_acZOS (Maybe User)
+ Calamity.Cache.Eff: getBotUser :: forall r_adJ5s. Member CacheEff r_adJ5s => Sem r_adJ5s (Maybe User)
- Calamity.Cache.Eff: getDM :: forall r_acZP4. Member CacheEff r_acZP4 => Snowflake DMChannel -> Sem r_acZP4 (Maybe DMChannel)
+ Calamity.Cache.Eff: getDM :: forall r_adJ5E. Member CacheEff r_adJ5E => Snowflake DMChannel -> Sem r_adJ5E (Maybe DMChannel)
- Calamity.Cache.Eff: getDMs :: forall r_acZP6. Member CacheEff r_acZP6 => Sem r_acZP6 [DMChannel]
+ Calamity.Cache.Eff: getDMs :: forall r_adJ5G. Member CacheEff r_adJ5G => Sem r_adJ5G [DMChannel]
- Calamity.Cache.Eff: getGuild :: forall r_acZOV. Member CacheEff r_acZOV => Snowflake Guild -> Sem r_acZOV (Maybe Guild)
+ Calamity.Cache.Eff: getGuild :: forall r_adJ5v. Member CacheEff r_adJ5v => Snowflake Guild -> Sem r_adJ5v (Maybe Guild)
- Calamity.Cache.Eff: getGuildChannel :: forall r_acZOX. Member CacheEff r_acZOX => Snowflake GuildChannel -> Sem r_acZOX (Maybe GuildChannel)
+ Calamity.Cache.Eff: getGuildChannel :: forall r_adJ5x. Member CacheEff r_adJ5x => Snowflake GuildChannel -> Sem r_adJ5x (Maybe GuildChannel)
- Calamity.Cache.Eff: getGuilds :: forall r_acZOZ. Member CacheEff r_acZOZ => Sem r_acZOZ [Guild]
+ Calamity.Cache.Eff: getGuilds :: forall r_adJ5z. Member CacheEff r_adJ5z => Sem r_adJ5z [Guild]
- Calamity.Cache.Eff: getMessage :: forall r_acZPp. Member CacheEff r_acZPp => Snowflake Message -> Sem r_acZPp (Maybe Message)
+ Calamity.Cache.Eff: getMessage :: forall r_adJ5Z. Member CacheEff r_adJ5Z => Snowflake Message -> Sem r_adJ5Z (Maybe Message)
- Calamity.Cache.Eff: getMessages :: forall r_acZPr. Member CacheEff r_acZPr => Sem r_acZPr [Message]
+ Calamity.Cache.Eff: getMessages :: forall r_adJ61. Member CacheEff r_adJ61 => Sem r_adJ61 [Message]
- Calamity.Cache.Eff: getUnavailableGuilds :: forall r_acZPk. Member CacheEff r_acZPk => Sem r_acZPk [Snowflake Guild]
+ Calamity.Cache.Eff: getUnavailableGuilds :: forall r_adJ5U. Member CacheEff r_adJ5U => Sem r_adJ5U [Snowflake Guild]
- Calamity.Cache.Eff: getUser :: forall r_acZPb. Member CacheEff r_acZPb => Snowflake User -> Sem r_acZPb (Maybe User)
+ Calamity.Cache.Eff: getUser :: forall r_adJ5L. Member CacheEff r_adJ5L => Snowflake User -> Sem r_adJ5L (Maybe User)
- Calamity.Cache.Eff: getUsers :: forall r_acZPd. Member CacheEff r_acZPd => Sem r_acZPd [User]
+ Calamity.Cache.Eff: getUsers :: forall r_adJ5N. Member CacheEff r_adJ5N => Sem r_adJ5N [User]
- Calamity.Cache.Eff: isUnavailableGuild :: forall r_acZPi. Member CacheEff r_acZPi => Snowflake Guild -> Sem r_acZPi Bool
+ Calamity.Cache.Eff: isUnavailableGuild :: forall r_adJ5S. Member CacheEff r_adJ5S => Snowflake Guild -> Sem r_adJ5S Bool
- Calamity.Cache.Eff: setBotUser :: forall r_acZOQ. Member CacheEff r_acZOQ => User -> Sem r_acZOQ ()
+ Calamity.Cache.Eff: setBotUser :: forall r_adJ5q. Member CacheEff r_adJ5q => User -> Sem r_adJ5q ()
- Calamity.Cache.Eff: setDM :: forall r_acZP2. Member CacheEff r_acZP2 => DMChannel -> Sem r_acZP2 ()
+ Calamity.Cache.Eff: setDM :: forall r_adJ5C. Member CacheEff r_adJ5C => DMChannel -> Sem r_adJ5C ()
- Calamity.Cache.Eff: setGuild :: forall r_acZOT. Member CacheEff r_acZOT => Guild -> Sem r_acZOT ()
+ Calamity.Cache.Eff: setGuild :: forall r_adJ5t. Member CacheEff r_adJ5t => Guild -> Sem r_adJ5t ()
- Calamity.Cache.Eff: setMessage :: forall r_acZPn. Member CacheEff r_acZPn => Message -> Sem r_acZPn ()
+ Calamity.Cache.Eff: setMessage :: forall r_adJ5X. Member CacheEff r_adJ5X => Message -> Sem r_adJ5X ()
- Calamity.Cache.Eff: setUnavailableGuild :: forall r_acZPg. Member CacheEff r_acZPg => Snowflake Guild -> Sem r_acZPg ()
+ Calamity.Cache.Eff: setUnavailableGuild :: forall r_adJ5Q. Member CacheEff r_adJ5Q => Snowflake Guild -> Sem r_adJ5Q ()
- Calamity.Cache.Eff: setUser :: forall r_acZP9. Member CacheEff r_acZP9 => User -> Sem r_acZP9 ()
+ Calamity.Cache.Eff: setUser :: forall r_adJ5J. Member CacheEff r_adJ5J => User -> Sem r_adJ5J ()
- Calamity.Client.Client: runBotIO'' :: forall r a. Members '[LogEff, MetricEff, CacheEff, Reader Client, AtomicState EventHandlers, Embed IO, Final IO, Async] r => Token -> Intents -> Maybe StatusUpdateData -> Sem (Reader Client : r) a -> Sem r (Maybe StartupError)
+ Calamity.Client.Client: runBotIO'' :: forall r a. Members '[LogEff, MetricEff, CacheEff, Reader Client, AtomicState EventHandlers, Embed IO, Final IO, Async] r => Token -> Intents -> Maybe StatusUpdateData -> Sem (RatelimitEff : (TokenEff : (Reader Client : r))) a -> Sem r (Maybe StartupError)
- Calamity.Client.Types: type BotC r = (Members '[LogEff, MetricEff, CacheEff, Reader Client, AtomicState EventHandlers, Embed IO, Final IO, Async] r)
+ Calamity.Client.Types: type BotC r = (Members '[LogEff, MetricEff, CacheEff, RatelimitEff, TokenEff, Reader Client, AtomicState EventHandlers, Embed IO, Final IO, Async] r)
- Calamity.Client.Types: type SetupEff r = (Reader Client : AtomicState EventHandlers : Async : r)
+ Calamity.Client.Types: type SetupEff r = (RatelimitEff : TokenEff : Reader Client : AtomicState EventHandlers : Async : r)
- Calamity.Gateway.DispatchEvents: GuildMemberAdd :: !Member -> DispatchData
+ Calamity.Gateway.DispatchEvents: GuildMemberAdd :: !Snowflake Guild -> !Member -> DispatchData
- Calamity.HTTP: invoke :: (BotC r, Request a, ReadResponse (Result a)) => a -> Sem r (Either RestError (Result a))
+ Calamity.HTTP: invoke :: (Members '[RatelimitEff, TokenEff, LogEff, MetricEff, Embed IO] r, Request a, ReadResponse (Result a)) => a -> Sem r (Either RestError (Result a))
- Calamity.HTTP.Channel: CreateMessageOptions :: Maybe Text -> Maybe Text -> Maybe Bool -> Maybe (Text, ByteString) -> Maybe Embed -> Maybe AllowedMentions -> Maybe MessageReference -> CreateMessageOptions
+ Calamity.HTTP.Channel: CreateMessageOptions :: Maybe Text -> Maybe Text -> Maybe Bool -> Maybe [CreateMessageAttachment] -> Maybe [Embed] -> Maybe AllowedMentions -> Maybe MessageReference -> Maybe [Component] -> CreateMessageOptions
- Calamity.HTTP.Guild: [GetGuildBans] :: HasID Guild g => g -> GuildRequest [BanData]
+ Calamity.HTTP.Guild: [GetGuildBans] :: HasID Guild g => g -> GetGuildBansOptions -> GuildRequest [BanData]
- Calamity.HTTP.Internal.Ratelimit: doRequest :: BotC r => RateLimitState -> Route -> IO LbsResponse -> Sem r (Either RestError ByteString)
+ Calamity.HTTP.Internal.Ratelimit: doRequest :: Members '[RatelimitEff, TokenEff, LogEff, Embed IO] r => RateLimitState -> Route -> IO LbsResponse -> Sem r (Either RestError ByteString)
- Calamity.HTTP.Internal.Request: invoke :: (BotC r, Request a, ReadResponse (Result a)) => a -> Sem r (Either RestError (Result a))
+ Calamity.HTTP.Internal.Request: invoke :: (Members '[RatelimitEff, TokenEff, LogEff, MetricEff, Embed IO] r, Request a, ReadResponse (Result a)) => a -> Sem r (Either RestError (Result a))
- Calamity.HTTP.Webhook: ExecuteWebhookOptions :: Maybe Bool -> Maybe Text -> Maybe ByteString -> Maybe [Embed] -> Maybe Text -> Maybe Text -> Maybe Bool -> ExecuteWebhookOptions
+ Calamity.HTTP.Webhook: ExecuteWebhookOptions :: Maybe Bool -> Maybe Text -> [CreateMessageAttachment] -> Maybe [Embed] -> Maybe Text -> Maybe Text -> Maybe AllowedMentions -> Maybe Bool -> [Component] -> ExecuteWebhookOptions
- Calamity.Internal.LocalWriter: llisten :: forall o_aspu r_asr0 a_Xspu. Member (LocalWriter o_aspu) r_asr0 => Sem r_asr0 a_Xspu -> Sem r_asr0 (o_aspu, a_Xspu)
+ Calamity.Internal.LocalWriter: llisten :: forall o_ayf2 r_aygy a_Xyf2. Member (LocalWriter o_ayf2) r_aygy => Sem r_aygy a_Xyf2 -> Sem r_aygy (o_ayf2, a_Xyf2)
- Calamity.Internal.LocalWriter: ltell :: forall o_aspq r_asqY. Member (LocalWriter o_aspq) r_asqY => o_aspq -> Sem r_asqY ()
+ Calamity.Internal.LocalWriter: ltell :: forall o_ayeY r_aygw. Member (LocalWriter o_ayeY) r_aygw => o_ayeY -> Sem r_aygw ()
- Calamity.Internal.Utils: whileMFinalIO :: Member (Final IO) r => Sem r Bool -> Sem r ()
+ Calamity.Internal.Utils: whileMFinalIO :: Sem r Bool -> Sem r ()
- Calamity.Metrics.Eff: addCounter :: forall r_aBLN. Member MetricEff r_aBLN => Int -> Counter -> Sem r_aBLN Int
+ Calamity.Metrics.Eff: addCounter :: forall r_aHee. Member MetricEff r_aHee => Int -> Counter -> Sem r_aHee Int
- Calamity.Metrics.Eff: modifyGauge :: forall r_aBLQ. Member MetricEff r_aBLQ => (Double -> Double) -> Gauge -> Sem r_aBLQ Double
+ Calamity.Metrics.Eff: modifyGauge :: forall r_aHeh. Member MetricEff r_aHeh => (Double -> Double) -> Gauge -> Sem r_aHeh Double
- Calamity.Metrics.Eff: observeHistogram :: forall r_aBLT. Member MetricEff r_aBLT => Double -> Histogram -> Sem r_aBLT HistogramSample
+ Calamity.Metrics.Eff: observeHistogram :: forall r_aHek. Member MetricEff r_aHek => Double -> Histogram -> Sem r_aHek HistogramSample
- Calamity.Metrics.Eff: registerCounter :: forall r_aBLD. Member MetricEff r_aBLD => Text -> [(Text, Text)] -> Sem r_aBLD Counter
+ Calamity.Metrics.Eff: registerCounter :: forall r_aHe4. Member MetricEff r_aHe4 => Text -> [(Text, Text)] -> Sem r_aHe4 Counter
- Calamity.Metrics.Eff: registerGauge :: forall r_aBLG. Member MetricEff r_aBLG => Text -> [(Text, Text)] -> Sem r_aBLG Gauge
+ Calamity.Metrics.Eff: registerGauge :: forall r_aHe7. Member MetricEff r_aHe7 => Text -> [(Text, Text)] -> Sem r_aHe7 Gauge
- Calamity.Metrics.Eff: registerHistogram :: forall r_aBLJ. Member MetricEff r_aBLJ => Text -> [(Text, Text)] -> [Double] -> Sem r_aBLJ Histogram
+ Calamity.Metrics.Eff: registerHistogram :: forall r_aHea. Member MetricEff r_aHea => Text -> [(Text, Text)] -> [Double] -> Sem r_aHea Histogram
- Calamity.Types.Model.Channel.Component: Button :: ButtonStyle -> Maybe Text -> Maybe RawEmoji -> Maybe Text -> Maybe Text -> Bool -> Button
+ Calamity.Types.Model.Channel.Component: Button :: ButtonStyle -> Maybe Text -> Maybe RawEmoji -> Bool -> CustomID -> Button
- Calamity.Types.Model.Channel.Component: [$sel:customID:Button] :: Button -> Maybe Text
+ Calamity.Types.Model.Channel.Component: [$sel:customID:Button] :: Button -> CustomID
- Calamity.Types.Model.Channel.Component: button :: ButtonStyle -> Button
+ Calamity.Types.Model.Channel.Component: button :: ButtonStyle -> CustomID -> Button
- Calamity.Types.Model.Channel.Component: button' :: ButtonStyle -> Text -> Button
+ Calamity.Types.Model.Channel.Component: button' :: ButtonStyle -> Text -> CustomID -> Button
- Calamity.Types.Model.Channel.Message: Message :: Snowflake Message -> Snowflake Channel -> Maybe (Snowflake Guild) -> Snowflake User -> Text -> UTCTime -> Maybe UTCTime -> Bool -> Bool -> Vector (Snowflake User) -> Vector (Snowflake Role) -> Maybe (Vector (Snowflake Channel)) -> ![Attachment] -> ![Embed] -> ![Reaction] -> Maybe Value -> Bool -> Maybe (Snowflake Webhook) -> !MessageType -> Maybe Object -> Maybe Object -> Maybe MessageReference -> Word64 -> Maybe [Object] -> Maybe Message -> Maybe Object -> [Component] -> Message
+ Calamity.Types.Model.Channel.Message: Message :: Snowflake Message -> Snowflake Channel -> Maybe (Snowflake Guild) -> Snowflake User -> Text -> UTCTime -> Maybe UTCTime -> Bool -> Bool -> Vector (Snowflake User) -> Vector (Snowflake Role) -> Maybe (Vector (Snowflake Channel)) -> ![Attachment] -> ![Embed] -> ![Reaction] -> Maybe Value -> Bool -> Maybe (Snowflake Webhook) -> !MessageType -> Maybe Object -> Maybe Object -> Maybe MessageReference -> Word64 -> Maybe Message -> Maybe Object -> [Component] -> Message
- Calamity.Types.Model.Channel.UpdatedMessage: UpdatedMessage :: Snowflake Message -> Snowflake Channel -> Maybe Text -> Maybe (MaybeNull UTCTime) -> Maybe Bool -> Maybe Bool -> Maybe (Vector (Snowflake User)) -> Maybe (Vector (Snowflake Role)) -> Maybe (Vector (Snowflake Channel)) -> !Maybe [Attachment] -> Maybe [Embed] -> Maybe [Reaction] -> Maybe Bool -> Maybe (MaybeNull (Snowflake Webhook)) -> Maybe MessageType -> Maybe (MaybeNull Object) -> Maybe (MaybeNull Object) -> Maybe (MaybeNull MessageReference) -> Maybe Word64 -> Maybe (MaybeNull [Object]) -> Maybe (MaybeNull Message) -> Maybe (MaybeNull Object) -> Maybe [Component] -> UpdatedMessage
+ Calamity.Types.Model.Channel.UpdatedMessage: UpdatedMessage :: Snowflake Message -> Snowflake Channel -> Maybe Text -> Maybe (MaybeNull UTCTime) -> Maybe Bool -> Maybe Bool -> Maybe (Vector (Snowflake User)) -> Maybe (Vector (Snowflake Role)) -> Maybe (Vector (Snowflake Channel)) -> !Maybe [Attachment] -> Maybe [Embed] -> Maybe [Reaction] -> Maybe Bool -> Maybe (MaybeNull (Snowflake Webhook)) -> Maybe MessageType -> Maybe (MaybeNull Object) -> Maybe (MaybeNull Object) -> Maybe (MaybeNull MessageReference) -> Maybe Word64 -> Maybe (MaybeNull Message) -> Maybe (MaybeNull Object) -> Maybe [Component] -> UpdatedMessage
- Calamity.Types.Model.Guild.Member: Member :: Snowflake User -> Text -> Text -> Maybe Bool -> Maybe Text -> Maybe Bool -> Maybe Bool -> Maybe Text -> Maybe Word64 -> Maybe Word64 -> Snowflake Guild -> Maybe Text -> Vector (Snowflake Role) -> UTCTime -> Bool -> Bool -> Member
+ Calamity.Types.Model.Guild.Member: Member :: Snowflake User -> Text -> Text -> Maybe Bool -> Maybe Text -> Maybe Bool -> Maybe Bool -> Maybe Text -> Maybe Word64 -> Maybe Word64 -> Maybe Text -> Vector (Snowflake Role) -> UTCTime -> Bool -> Bool -> Member
- Calamity.Types.Model.Interaction: Interaction :: Snowflake Interaction -> Snowflake () -> InteractionType -> Maybe ApplicationCommandInteractionData -> Maybe (Snowflake Guild) -> Maybe (Snowflake Channel) -> Maybe Member -> Maybe User -> Text -> Int -> Maybe Message -> Interaction
+ Calamity.Types.Model.Interaction: Interaction :: Snowflake Interaction -> Snowflake Application -> InteractionType -> Maybe InteractionData -> Maybe (Snowflake Guild) -> Maybe (Snowflake Channel) -> Maybe Member -> Maybe User -> InteractionToken -> Int -> Maybe Message -> Maybe Text -> Maybe Text -> Interaction
- Calamity.Types.Model.Interaction: [$sel:applicationID:Interaction] :: Interaction -> Snowflake ()
+ Calamity.Types.Model.Interaction: [$sel:applicationID:Interaction] :: Interaction -> Snowflake Application
- Calamity.Types.Model.Interaction: [$sel:data_:Interaction] :: Interaction -> Maybe ApplicationCommandInteractionData
+ Calamity.Types.Model.Interaction: [$sel:data_:Interaction] :: Interaction -> Maybe InteractionData
- Calamity.Types.Model.Interaction: [$sel:token:Interaction] :: Interaction -> Text
+ Calamity.Types.Model.Interaction: [$sel:token:Interaction] :: Interaction -> InteractionToken

Files

Calamity/Client/Client.hs view
@@ -2,18 +2,18 @@  -- | The client module Calamity.Client.Client (-    react,-    runBotIO,-    runBotIO',-    runBotIO'',-    stopBot,-    sendPresence,-    events,-    fire,-    waitUntil,-    waitUntilM,-    CalamityEvent (Dispatch, ShutDown),-    customEvt,+  react,+  runBotIO,+  runBotIO',+  runBotIO'',+  stopBot,+  sendPresence,+  events,+  fire,+  waitUntil,+  waitUntilM,+  CalamityEvent (Dispatch, ShutDown),+  customEvt, ) where  import Calamity.Cache.Eff@@ -23,6 +23,7 @@ import Calamity.Gateway.Intents import Calamity.Gateway.Types import Calamity.HTTP.Internal.Ratelimit+import Calamity.Types.TokenEff import Calamity.Internal.ConstructorName import Calamity.Internal.RunIntoIO import qualified Calamity.Internal.SnowflakeMap as SM@@ -68,74 +69,88 @@  timeA :: P.Member (P.Embed IO) r => P.Sem r a -> P.Sem r (Double, a) timeA m = do-    start <- P.embed getPOSIXTime-    res <- m-    end <- P.embed getPOSIXTime-    let duration = fromRational . toRational $ end - start-    pure (duration, res)+  start <- P.embed getPOSIXTime+  res <- m+  end <- P.embed getPOSIXTime+  let duration = fromRational . toRational $ end - start+  pure (duration, res)  newClient :: Token -> Maybe (DC.Di Df1.Level Df1.Path Df1.Message) -> IO Client newClient token initialDi = do-    shards' <- newTVarIO []-    numShards' <- newEmptyMVar-    rlState' <- newRateLimitState-    (inc, outc) <- newChan-    ehidCounter <- newIORef 0+  shards' <- newTVarIO []+  numShards' <- newEmptyMVar+  rlState' <- newRateLimitState+  (inc, outc) <- newChan+  ehidCounter <- newIORef 0 -    pure $-        Client-            shards'-            numShards'-            token-            rlState'-            inc-            outc-            ehidCounter-            initialDi+  pure $+    Client+      shards'+      numShards'+      token+      rlState'+      inc+      outc+      ehidCounter+      initialDi  -- | Create a bot, run your setup action, and then loop until the bot closes. runBotIO ::-    forall r a.-    (P.Members '[P.Embed IO, P.Final IO, CacheEff, MetricEff, LogEff] r) =>-    Token ->-    -- | The intents the bot should use-    Intents ->-    P.Sem (SetupEff r) a ->-    P.Sem r (Maybe StartupError)+  forall r a.+  (P.Members '[P.Embed IO, P.Final IO, CacheEff, MetricEff, LogEff] r) =>+  Token ->+  -- | The intents the bot should use+  Intents ->+  P.Sem (SetupEff r) a ->+  P.Sem r (Maybe StartupError) runBotIO token intents = runBotIO' token intents Nothing  resetDi :: BotC r => P.Sem r a -> P.Sem r a resetDi m = do-    initialDi <- P.asks (^. #initialDi)-    Di.local (`fromMaybe` initialDi) m+  initialDi <- P.asks (^. #initialDi)+  Di.local (`fromMaybe` initialDi) m +interpretRatelimitViaClient :: P.Member (P.Reader Client) r => P.Sem (RatelimitEff ': r) a -> P.Sem r a+interpretRatelimitViaClient =+  P.interpret+    ( \case+        GetRatelimitState -> P.asks (^. #rlState)+    )++interpretTokenViaClient :: P.Member (P.Reader Client) r => P.Sem (TokenEff ': r) a -> P.Sem r a+interpretTokenViaClient =+  P.interpret+    ( \case+        GetBotToken -> P.asks (^. #token)+    )+ {- | Create a bot, run your setup action, and then loop until the bot closes.   This version allows you to specify the initial status -} runBotIO' ::-    forall r a.-    (P.Members '[P.Embed IO, P.Final IO, CacheEff, MetricEff, LogEff] r) =>-    Token ->-    -- | The intents the bot should use-    Intents ->-    -- | The initial status to send to the gateway-    Maybe StatusUpdateData ->-    P.Sem (SetupEff r) a ->-    P.Sem r (Maybe StartupError)+  forall r a.+  (P.Members '[P.Embed IO, P.Final IO, CacheEff, MetricEff, LogEff] r) =>+  Token ->+  -- | The intents the bot should use+  Intents ->+  -- | The initial status to send to the gateway+  Maybe StatusUpdateData ->+  P.Sem (SetupEff r) a ->+  P.Sem r (Maybe StartupError) runBotIO' token intents status setup = do-    initialDi <- Di.fetch-    client <- P.embed $ newClient token initialDi-    handlers <- P.embed $ newTVarIO def-    P.asyncToIOFinal . P.runAtomicStateTVar handlers . P.runReader client . Di.push "calamity" $ do-        void $ Di.push "calamity-setup" setup-        r <- shardBot status intents-        case r of-            Left e -> pure (Just e)-            Right _ -> do-                Di.push "calamity-loop" clientLoop-                Di.push "calamity-stop" finishUp-                pure Nothing+  initialDi <- Di.fetch+  client <- P.embed $ newClient token initialDi+  handlers <- P.embed $ newTVarIO def+  P.asyncToIOFinal . P.runAtomicStateTVar handlers . P.runReader client . interpretTokenViaClient . interpretRatelimitViaClient . Di.push "calamity" $ do+    void $ Di.push "calamity-setup" setup+    r <- shardBot status intents+    case r of+      Left e -> pure (Just e)+      Right _ -> do+        Di.push "calamity-loop" clientLoop+        Di.push "calamity-stop" finishUp+        pure Nothing  {- | Create a bot, run your setup action, and then loop until the bot closes. @@ -143,8 +158,8 @@  handle the @'P.AtomicState' 'EventHandlers'@ yourself. -} runBotIO'' ::-    forall r a.-    (P.Members+  forall r a.+  ( P.Members       '[ LogEff        , MetricEff        , CacheEff@@ -154,26 +169,27 @@        , P.Final IO        , P.Async        ]- r) =>-    Token ->-    -- | The intents the bot should use-    Intents ->-    -- | The initial status to send to the gateway-    Maybe StatusUpdateData ->-    P.Sem (P.Reader Client ': r) a ->-    P.Sem r (Maybe StartupError)+      r+  ) =>+  Token ->+  -- | The intents the bot should use+  Intents ->+  -- | The initial status to send to the gateway+  Maybe StatusUpdateData ->+  P.Sem (RatelimitEff ': TokenEff ': P.Reader Client ': r) a ->+  P.Sem r (Maybe StartupError) runBotIO'' token intents status setup = do-    initialDi <- Di.fetch-    client <- P.embed $ newClient token initialDi-    P.runReader client . Di.push "calamity" $ do-        void $ Di.push "calamity-setup" setup-        r <- shardBot status intents-        case r of-            Left e -> pure (Just e)-            Right _ -> do-                Di.push "calamity-loop" clientLoop-                Di.push "calamity-stop" finishUp-                pure Nothing+  initialDi <- Di.fetch+  client <- P.embed $ newClient token initialDi+  P.runReader client . interpretTokenViaClient . interpretRatelimitViaClient . Di.push "calamity" $ do+    void $ Di.push "calamity-setup" setup+    r <- shardBot status intents+    case r of+      Left e -> pure (Just e)+      Right _ -> do+        Di.push "calamity-loop" clientLoop+        Di.push "calamity-stop" finishUp+        pure Nothing  {- | Register an event handler, returning an action that removes the event handler from the bot. @@ -207,17 +223,17 @@  which will result in errors about parameter counts mismatching. -} react ::-    forall (s :: EventType) r.-    (BotC r, ReactConstraints s) =>-    (EHType s -> (P.Sem r) ()) ->-    P.Sem r (P.Sem r ())+  forall (s :: EventType) r.+  (BotC r, ReactConstraints s) =>+  (EHType s -> (P.Sem r) ()) ->+  P.Sem r (P.Sem r ()) react handler = do-    handler' <- bindSemToIO handler-    ehidC <- P.asks (^. #ehidCounter)-    id' <- P.embed $ atomicModifyIORef ehidC (\i -> (i + 1, i))-    let handlers = makeEventHandlers (Proxy @s) id' (const () <.> handler')-    P.atomicModify (handlers <>)-    pure $ removeHandler @s id'+  handler' <- bindSemToIO handler+  ehidC <- P.asks (^. #ehidCounter)+  id' <- P.embed $ atomicModifyIORef ehidC (\i -> (i + 1, i))+  let handlers = makeEventHandlers (Proxy @s) id' (const () <.> handler')+  P.atomicModify (handlers <>)+  pure $ removeHandler @s id'  removeHandler :: forall (s :: EventType) r. (BotC r, RemoveEventHandler s) => Integer -> P.Sem r () removeHandler id' = P.atomicModify (removeEventHandler (Proxy @s) id')@@ -234,8 +250,8 @@ -} fire :: BotC r => CalamityEvent -> P.Sem r () fire e = do-    inc <- P.asks (^. #eventsIn)-    P.embed $ writeChan inc e+  inc <- P.asks (^. #eventsIn)+  P.embed $ writeChan inc e  {- | Build a Custom CalamityEvent @@ -253,8 +269,8 @@ -- | Get a copy of the event stream. events :: BotC r => P.Sem r (OutChan CalamityEvent) events = do-    inc <- P.asks (^. #eventsIn)-    P.embed $ dupChan inc+  inc <- P.asks (^. #eventsIn)+  P.embed $ dupChan inc  {- | Wait until an event satisfying a condition happens, then returns its  parameters.@@ -283,21 +299,21 @@  @ -} waitUntil ::-    forall (s :: EventType) r.-    (BotC r, ReactConstraints s) =>-    (EHType s -> Bool) ->-    P.Sem r (EHType s)+  forall (s :: EventType) r.+  (BotC r, ReactConstraints s) =>+  (EHType s -> Bool) ->+  P.Sem r (EHType s) waitUntil f = P.resourceToIOFinal $ do-    result <- P.embed newEmptyMVar-    P.bracket-        (P.raise $ react @s (checker result))-        P.raise-        (const . P.embed $ takeMVar result)+  result <- P.embed newEmptyMVar+  P.bracket+    (P.raise $ react @s (checker result))+    P.raise+    (const . P.embed $ takeMVar result)   where     checker :: MVar (EHType s) -> EHType s -> P.Sem r ()     checker result args = do-        when (f args) $ do-            P.embed $ putMVar result args+      when (f args) $ do+        P.embed $ putMVar result args  {- | Wait until an event satisfying a condition happens, then returns its  parameters@@ -324,135 +340,135 @@  @ -} waitUntilM ::-    forall (s :: EventType) r.-    (BotC r, ReactConstraints s) =>-    (EHType s -> P.Sem r Bool) ->-    P.Sem r (EHType s)+  forall (s :: EventType) r.+  (BotC r, ReactConstraints s) =>+  (EHType s -> P.Sem r Bool) ->+  P.Sem r (EHType s) waitUntilM f = P.resourceToIOFinal $ do-    result <- P.embed newEmptyMVar-    P.bracket-        (P.raise $ react @s (checker result))-        P.raise-        (const . P.embed $ takeMVar result)+  result <- P.embed newEmptyMVar+  P.bracket+    (P.raise $ react @s (checker result))+    P.raise+    (const . P.embed $ takeMVar result)   where     checker :: MVar (EHType s) -> EHType s -> P.Sem r ()     checker result args = do-        res <- f args-        when res $ do-            P.embed $ putMVar result args+      res <- f args+      when res $ do+        P.embed $ putMVar result args  -- | Set the bot's presence on all shards. sendPresence :: BotC r => StatusUpdateData -> P.Sem r () sendPresence s = do-    shards <- P.asks (^. #shards) >>= P.embed . readTVarIO-    for_ shards $ \(inc, _) ->-        P.embed $ writeChan inc (SendPresence s)+  shards <- P.asks (^. #shards) >>= P.embed . readTVarIO+  for_ shards $ \(inc, _) ->+    P.embed $ writeChan inc (SendPresence s)  -- | Initiate shutting down the bot. stopBot :: BotC r => P.Sem r () stopBot = do-    debug "stopping bot"-    inc <- P.asks (^. #eventsIn)-    P.embed $ writeChan inc ShutDown+  debug "stopping bot"+  inc <- P.asks (^. #eventsIn)+  P.embed $ writeChan inc ShutDown  finishUp :: BotC r => P.Sem r () finishUp = do-    debug "finishing up"-    shards <- P.asks (^. #shards) >>= P.embed . readTVarIO-    for_ shards $ \(inc, _) ->-        P.embed $ writeChan inc ShutDownShard-    for_ shards $ \(_, shardThread) -> P.await shardThread-    debug "bot has stopped"+  debug "finishing up"+  shards <- P.asks (^. #shards) >>= P.embed . readTVarIO+  for_ shards $ \(inc, _) ->+    P.embed $ writeChan inc ShutDownShard+  for_ shards $ \(_, shardThread) -> P.await shardThread+  debug "bot has stopped"  {- | main loop of the client, handles fetching the next event, processing the  event and invoking its handler functions -} clientLoop :: BotC r => P.Sem r () clientLoop = do-    outc <- P.asks (^. #eventsOut)-    whileMFinalIO $ do-        !evt' <- P.embed $ readChan outc-        case evt' of-            Dispatch !sid !evt -> handleEvent sid evt >> pure True-            Custom d -> handleCustomEvent d >> pure True-            ShutDown -> pure False-    debug "leaving client loop"+  outc <- P.asks (^. #eventsOut)+  whileMFinalIO $ do+    !evt' <- P.embed $ readChan outc+    case evt' of+      Dispatch !sid !evt -> handleEvent sid evt >> pure True+      Custom d -> handleCustomEvent d >> pure True+      ShutDown -> pure False+  debug "leaving client loop"  handleCustomEvent :: forall a r. (Typeable a, BotC r) => a -> P.Sem r () handleCustomEvent d = do-    eventHandlers <- P.atomicGet+  eventHandlers <- P.atomicGet -    let handlers = getCustomEventHandlers @a eventHandlers+  let handlers = getCustomEventHandlers @a eventHandlers -    for_ handlers (\h -> P.async . P.embed $ h d)+  for_ handlers (\h -> P.async . P.embed $ h d)  catchAllLogging :: BotC r => P.Sem r () -> P.Sem r () catchAllLogging m = do-    r <- P.errorToIOFinal . P.fromExceptionSem @SomeException $ P.raise m-    case r of-        Right _ -> pure ()-        Left e -> debug [fmt|got exception: {e:s}|]+  r <- P.errorToIOFinal . P.fromExceptionSem @SomeException $ P.raise m+  case r of+    Right _ -> pure ()+    Left e -> debug [fmt|got exception: {e:s}|]  handleEvent :: BotC r => Int -> DispatchData -> P.Sem r () handleEvent shardID data' = do-    debug "handling an event"-    eventHandlers <- P.atomicGet-    actions <- P.runFail $ do-        evtCounter <- registerCounter "events_received" [("type", T.pack $ ctorName data'), ("shard", showt shardID)]-        void $ addCounter 1 evtCounter-        cacheUpdateHisto <- registerHistogram "cache_update" mempty [10, 20 .. 100]-        (time, res) <- timeA . resetDi $ handleEvent' eventHandlers data'-        void $ observeHistogram time cacheUpdateHisto-        pure res+  debug [fmt|"handling an event: {ctorName data'}"|]+  eventHandlers <- P.atomicGet+  actions <- P.runFail $ do+    evtCounter <- registerCounter "events_received" [("type", T.pack $ ctorName data'), ("shard", showt shardID)]+    void $ addCounter 1 evtCounter+    cacheUpdateHisto <- registerHistogram "cache_update" mempty [10, 20 .. 100]+    (time, res) <- timeA . resetDi $ handleEvent' eventHandlers data'+    void $ observeHistogram time cacheUpdateHisto+    pure res -    eventHandleHisto <- registerHistogram "event_handle" mempty [10, 20 .. 100]+  eventHandleHisto <- registerHistogram "event_handle" mempty [10, 20 .. 100] -    case actions of-        Right actions -> for_ actions $ \action -> P.async $ do-            (time, _) <- timeA . catchAllLogging $ P.embed action-            void $ observeHistogram time eventHandleHisto-        -- pattern match failures are usually stuff like events for uncached guilds, etc-        Left err -> debug [fmt|Failed handling actions for event: {err:s}|]+  case actions of+    Right actions -> for_ actions $ \action -> P.async $ do+      (time, _) <- timeA . catchAllLogging $ P.embed action+      void $ observeHistogram time eventHandleHisto+    -- pattern match failures are usually stuff like events for uncached guilds, etc+    Left err -> debug [fmt|Failed handling actions for event: {err:s}|]  handleEvent' ::-    BotC r =>-    EventHandlers ->-    DispatchData ->-    P.Sem (P.Fail ': r) [IO ()]-handleEvent' eh evt@(Ready rd@ReadyData{}) = do-    updateCache evt-    pure $ map ($ rd) (getEventHandlers @'ReadyEvt eh)+  BotC r =>+  EventHandlers ->+  DispatchData ->+  P.Sem (P.Fail ': r) [IO ()]+handleEvent' eh evt@(Ready rd@ReadyData {}) = do+  updateCache evt+  pure $ map ($ rd) (getEventHandlers @'ReadyEvt eh) handleEvent' _ Resumed = pure [] handleEvent' eh evt@(ChannelCreate (DMChannel' chan)) = do-    updateCache evt-    Just newChan <- DMChannel' <<$>> getDM (getID chan)-    pure $ map ($ newChan) (getEventHandlers @'ChannelCreateEvt eh)+  updateCache evt+  Just newChan <- DMChannel' <<$>> getDM (getID chan)+  pure $ map ($ newChan) (getEventHandlers @'ChannelCreateEvt eh) handleEvent' eh evt@(ChannelCreate (GuildChannel' chan)) = do-    updateCache evt-    Just guild <- getGuild (getID chan)-    Just newChan <- pure $ GuildChannel' <$> guild ^. #channels . at (getID chan)-    pure $ map ($ newChan) (getEventHandlers @'ChannelCreateEvt eh)+  updateCache evt+  Just guild <- getGuild (getID chan)+  Just newChan <- pure $ GuildChannel' <$> guild ^. #channels . at (getID chan)+  pure $ map ($ newChan) (getEventHandlers @'ChannelCreateEvt eh) handleEvent' eh evt@(ChannelUpdate (DMChannel' chan)) = do-    Just oldChan <- DMChannel' <<$>> getDM (getID chan)-    updateCache evt-    Just newChan <- DMChannel' <<$>> getDM (getID chan)-    pure $ map ($ (oldChan, newChan)) (getEventHandlers @'ChannelUpdateEvt eh)+  Just oldChan <- DMChannel' <<$>> getDM (getID chan)+  updateCache evt+  Just newChan <- DMChannel' <<$>> getDM (getID chan)+  pure $ map ($ (oldChan, newChan)) (getEventHandlers @'ChannelUpdateEvt eh) handleEvent' eh evt@(ChannelUpdate (GuildChannel' chan)) = do-    Just oldGuild <- getGuild (getID chan)-    Just oldChan <- pure $ GuildChannel' <$> oldGuild ^. #channels . at (getID chan)-    updateCache evt-    Just newGuild <- getGuild (getID chan)-    Just newChan <- pure $ GuildChannel' <$> newGuild ^. #channels . at (getID chan)-    pure $ map ($ (oldChan, newChan)) (getEventHandlers @'ChannelUpdateEvt eh)+  Just oldGuild <- getGuild (getID chan)+  Just oldChan <- pure $ GuildChannel' <$> oldGuild ^. #channels . at (getID chan)+  updateCache evt+  Just newGuild <- getGuild (getID chan)+  Just newChan <- pure $ GuildChannel' <$> newGuild ^. #channels . at (getID chan)+  pure $ map ($ (oldChan, newChan)) (getEventHandlers @'ChannelUpdateEvt eh) handleEvent' eh evt@(ChannelDelete (GuildChannel' chan)) = do-    Just oldGuild <- getGuild (getID chan)-    Just oldChan <- pure $ GuildChannel' <$> oldGuild ^. #channels . at (getID chan)-    updateCache evt-    pure $ map ($ oldChan) (getEventHandlers @'ChannelDeleteEvt eh)+  Just oldGuild <- getGuild (getID chan)+  Just oldChan <- pure $ GuildChannel' <$> oldGuild ^. #channels . at (getID chan)+  updateCache evt+  pure $ map ($ oldChan) (getEventHandlers @'ChannelDeleteEvt eh) handleEvent' eh evt@(ChannelDelete (DMChannel' chan)) = do-    Just oldChan <- DMChannel' <<$>> getDM (getID chan)-    updateCache evt-    pure $ map ($ oldChan) (getEventHandlers @'ChannelDeleteEvt eh)+  Just oldChan <- DMChannel' <<$>> getDM (getID chan)+  updateCache evt+  pure $ map ($ oldChan) (getEventHandlers @'ChannelDeleteEvt eh)  -- handleEvent' eh evt@(ChannelPinsUpdate ChannelPinsUpdateData { channelID, lastPinTimestamp }) = do --   chan <- (GuildChannel' <$> os ^? #channels . at (coerceSnowflake channelID) . _Just)@@ -460,276 +476,276 @@ --   pure $ map (\f -> f chan lastPinTimestamp) (getEventHandlers @"channelpinsupdate" eh)  handleEvent' eh evt@(GuildCreate guild) = do-    isNew <- not <$> isUnavailableGuild (getID guild)-    updateCache evt-    Just guild <- getGuild (getID guild)-    pure $-        map-            ($ (guild, if isNew then GuildCreateNew else GuildCreateAvailable))-            (getEventHandlers @'GuildCreateEvt eh)+  isNew <- not <$> isUnavailableGuild (getID guild)+  updateCache evt+  Just guild <- getGuild (getID guild)+  pure $+    map+      ($ (guild, if isNew then GuildCreateNew else GuildCreateAvailable))+      (getEventHandlers @'GuildCreateEvt eh) handleEvent' eh evt@(GuildUpdate guild) = do-    Just oldGuild <- getGuild (getID guild)-    updateCache evt-    Just newGuild <- getGuild (getID guild)-    pure $ map ($ (oldGuild, newGuild)) (getEventHandlers @'GuildUpdateEvt eh)+  Just oldGuild <- getGuild (getID guild)+  updateCache evt+  Just newGuild <- getGuild (getID guild)+  pure $ map ($ (oldGuild, newGuild)) (getEventHandlers @'GuildUpdateEvt eh)  -- NOTE: Guild will be deleted in the new cache if unavailable was false-handleEvent' eh evt@(GuildDelete UnavailableGuild{id, unavailable}) = do-    Just oldGuild <- getGuild id-    updateCache evt-    pure $-        map-            ($ (oldGuild, if unavailable then GuildDeleteUnavailable else GuildDeleteRemoved))-            (getEventHandlers @'GuildDeleteEvt eh)-handleEvent' eh evt@(GuildBanAdd BanData{guildID, user}) = do-    Just guild <- getGuild guildID-    updateCache evt-    pure $ map ($ (guild, user)) (getEventHandlers @'GuildBanAddEvt eh)-handleEvent' eh evt@(GuildBanRemove BanData{guildID, user}) = do-    Just guild <- getGuild guildID-    updateCache evt-    pure $ map ($ (guild, user)) (getEventHandlers @'GuildBanRemoveEvt eh)+handleEvent' eh evt@(GuildDelete UnavailableGuild {id, unavailable}) = do+  Just oldGuild <- getGuild id+  updateCache evt+  pure $+    map+      ($ (oldGuild, if unavailable then GuildDeleteUnavailable else GuildDeleteRemoved))+      (getEventHandlers @'GuildDeleteEvt eh)+handleEvent' eh evt@(GuildBanAdd BanData {guildID, user}) = do+  Just guild <- getGuild guildID+  updateCache evt+  pure $ map ($ (guild, user)) (getEventHandlers @'GuildBanAddEvt eh)+handleEvent' eh evt@(GuildBanRemove BanData {guildID, user}) = do+  Just guild <- getGuild guildID+  updateCache evt+  pure $ map ($ (guild, user)) (getEventHandlers @'GuildBanRemoveEvt eh)  -- NOTE: we fire this event using the guild data with old emojis-handleEvent' eh evt@(GuildEmojisUpdate GuildEmojisUpdateData{guildID, emojis}) = do-    Just guild <- getGuild guildID-    updateCache evt-    pure $ map ($ (guild, emojis)) (getEventHandlers @'GuildEmojisUpdateEvt eh)-handleEvent' eh evt@(GuildIntegrationsUpdate GuildIntegrationsUpdateData{guildID}) = do-    updateCache evt-    Just guild <- getGuild guildID-    pure $ map ($ guild) (getEventHandlers @'GuildIntegrationsUpdateEvt eh)-handleEvent' eh evt@(GuildMemberAdd member) = do-    updateCache evt-    Just guild <- getGuild (getID member)-    Just member <- pure $ guild ^. #members . at (getID member)-    pure $ map ($ member) (getEventHandlers @'GuildMemberAddEvt eh)-handleEvent' eh evt@(GuildMemberRemove GuildMemberRemoveData{user, guildID}) = do-    Just guild <- getGuild guildID-    Just member <- pure $ guild ^. #members . at (getID user)-    updateCache evt-    pure $ map ($ member) (getEventHandlers @'GuildMemberRemoveEvt eh)-handleEvent' eh evt@(GuildMemberUpdate GuildMemberUpdateData{user, guildID}) = do-    Just oldGuild <- getGuild guildID-    Just oldMember <- pure $ oldGuild ^. #members . at (getID user)-    updateCache evt-    Just newGuild <- getGuild guildID-    Just newMember <- pure $ newGuild ^. #members . at (getID user)-    pure $ map ($ (oldMember, newMember)) (getEventHandlers @'GuildMemberUpdateEvt eh)-handleEvent' eh evt@(GuildMembersChunk GuildMembersChunkData{members, guildID}) = do-    updateCache evt-    Just guild <- getGuild guildID-    let memberIDs = map (getID @Member) members-    let members' = mapMaybe (\mid -> guild ^. #members . at mid) memberIDs-    pure $ map ($ (guild, members')) (getEventHandlers @'GuildMembersChunkEvt eh)-handleEvent' eh evt@(GuildRoleCreate GuildRoleData{guildID, role}) = do-    updateCache evt-    Just guild <- getGuild guildID-    Just role' <- pure $ guild ^. #roles . at (getID role)-    pure $ map ($ (guild, role')) (getEventHandlers @'GuildRoleCreateEvt eh)-handleEvent' eh evt@(GuildRoleUpdate GuildRoleData{guildID, role}) = do-    Just oldGuild <- getGuild guildID-    Just oldRole <- pure $ oldGuild ^. #roles . at (getID role)-    updateCache evt-    Just newGuild <- getGuild guildID-    Just newRole <- pure $ newGuild ^. #roles . at (getID role)-    pure $ map ($ (newGuild, oldRole, newRole)) (getEventHandlers @'GuildRoleUpdateEvt eh)-handleEvent' eh evt@(GuildRoleDelete GuildRoleDeleteData{guildID, roleID}) = do-    Just guild <- getGuild guildID-    Just role <- pure $ guild ^. #roles . at roleID-    updateCache evt-    pure $ map ($ (guild, role)) (getEventHandlers @'GuildRoleDeleteEvt eh)+handleEvent' eh evt@(GuildEmojisUpdate GuildEmojisUpdateData {guildID, emojis}) = do+  Just guild <- getGuild guildID+  updateCache evt+  pure $ map ($ (guild, emojis)) (getEventHandlers @'GuildEmojisUpdateEvt eh)+handleEvent' eh evt@(GuildIntegrationsUpdate GuildIntegrationsUpdateData {guildID}) = do+  updateCache evt+  Just guild <- getGuild guildID+  pure $ map ($ guild) (getEventHandlers @'GuildIntegrationsUpdateEvt eh)+handleEvent' eh evt@(GuildMemberAdd gid member) = do+  updateCache evt+  Just guild <- getGuild gid+  Just member <- pure $ guild ^. #members . at (getID member)+  pure $ map ($ member) (getEventHandlers @'GuildMemberAddEvt eh)+handleEvent' eh evt@(GuildMemberRemove GuildMemberRemoveData {user, guildID}) = do+  Just guild <- getGuild guildID+  Just member <- pure $ guild ^. #members . at (getID user)+  updateCache evt+  pure $ map ($ member) (getEventHandlers @'GuildMemberRemoveEvt eh)+handleEvent' eh evt@(GuildMemberUpdate GuildMemberUpdateData {user, guildID}) = do+  Just oldGuild <- getGuild guildID+  Just oldMember <- pure $ oldGuild ^. #members . at (getID user)+  updateCache evt+  Just newGuild <- getGuild guildID+  Just newMember <- pure $ newGuild ^. #members . at (getID user)+  pure $ map ($ (oldMember, newMember)) (getEventHandlers @'GuildMemberUpdateEvt eh)+handleEvent' eh evt@(GuildMembersChunk GuildMembersChunkData {members, guildID}) = do+  updateCache evt+  Just guild <- getGuild guildID+  let memberIDs = map (getID @Member) members+  let members' = mapMaybe (\mid -> guild ^. #members . at mid) memberIDs+  pure $ map ($ (guild, members')) (getEventHandlers @'GuildMembersChunkEvt eh)+handleEvent' eh evt@(GuildRoleCreate GuildRoleData {guildID, role}) = do+  updateCache evt+  Just guild <- getGuild guildID+  Just role' <- pure $ guild ^. #roles . at (getID role)+  pure $ map ($ (guild, role')) (getEventHandlers @'GuildRoleCreateEvt eh)+handleEvent' eh evt@(GuildRoleUpdate GuildRoleData {guildID, role}) = do+  Just oldGuild <- getGuild guildID+  Just oldRole <- pure $ oldGuild ^. #roles . at (getID role)+  updateCache evt+  Just newGuild <- getGuild guildID+  Just newRole <- pure $ newGuild ^. #roles . at (getID role)+  pure $ map ($ (newGuild, oldRole, newRole)) (getEventHandlers @'GuildRoleUpdateEvt eh)+handleEvent' eh evt@(GuildRoleDelete GuildRoleDeleteData {guildID, roleID}) = do+  Just guild <- getGuild guildID+  Just role <- pure $ guild ^. #roles . at roleID+  updateCache evt+  pure $ map ($ (guild, role)) (getEventHandlers @'GuildRoleDeleteEvt eh) handleEvent' eh (InviteCreate d) = do-    pure $ map ($ d) (getEventHandlers @'InviteCreateEvt eh)+  pure $ map ($ d) (getEventHandlers @'InviteCreateEvt eh) handleEvent' eh (InviteDelete d) = do-    pure $ map ($ d) (getEventHandlers @'InviteDeleteEvt eh)+  pure $ map ($ d) (getEventHandlers @'InviteDeleteEvt eh) handleEvent' eh evt@(MessageCreate msg user member) = do-    updateCache evt-    pure $ map ($ (msg, user, member)) (getEventHandlers @'MessageCreateEvt eh)+  updateCache evt+  pure $ map ($ (msg, user, member)) (getEventHandlers @'MessageCreateEvt eh) handleEvent' eh evt@(MessageUpdate msg user member) = do-    oldMsg <- getMessage (getID msg)-    updateCache evt-    newMsg <- getMessage (getID msg)-    let rawActions = map ($ (msg, user, member)) (getEventHandlers @'RawMessageUpdateEvt eh)-    let actions = case (oldMsg, newMsg) of-            (Just oldMsg', Just newMsg') ->-                map ($ (oldMsg', newMsg', user, member)) (getEventHandlers @'MessageUpdateEvt eh)-            _ -> []-    pure $ rawActions <> actions-handleEvent' eh evt@(MessageDelete MessageDeleteData{id}) = do-    oldMsg <- getMessage id-    updateCache evt-    let rawActions = map ($ id) (getEventHandlers @'RawMessageDeleteEvt eh)-    let actions = case oldMsg of-            Just oldMsg' ->-                map ($ oldMsg') (getEventHandlers @'MessageDeleteEvt eh)-            _ -> []-    pure $ rawActions <> actions-handleEvent' eh evt@(MessageDeleteBulk MessageDeleteBulkData{ids}) = do-    messages <- catMaybes <$> traverse getMessage ids-    updateCache evt-    let rawActions = map ($ ids) (getEventHandlers @'RawMessageDeleteBulkEvt eh)-    let actions = map ($ messages) (getEventHandlers @'MessageDeleteBulkEvt eh)-    pure $ rawActions <> actions+  oldMsg <- getMessage (getID msg)+  updateCache evt+  newMsg <- getMessage (getID msg)+  let rawActions = map ($ (msg, user, member)) (getEventHandlers @'RawMessageUpdateEvt eh)+  let actions = case (oldMsg, newMsg) of+        (Just oldMsg', Just newMsg') ->+          map ($ (oldMsg', newMsg', user, member)) (getEventHandlers @'MessageUpdateEvt eh)+        _ -> []+  pure $ rawActions <> actions+handleEvent' eh evt@(MessageDelete MessageDeleteData {id}) = do+  oldMsg <- getMessage id+  updateCache evt+  let rawActions = map ($ id) (getEventHandlers @'RawMessageDeleteEvt eh)+  let actions = case oldMsg of+        Just oldMsg' ->+          map ($ oldMsg') (getEventHandlers @'MessageDeleteEvt eh)+        _ -> []+  pure $ rawActions <> actions+handleEvent' eh evt@(MessageDeleteBulk MessageDeleteBulkData {ids}) = do+  messages <- catMaybes <$> traverse getMessage ids+  updateCache evt+  let rawActions = map ($ ids) (getEventHandlers @'RawMessageDeleteBulkEvt eh)+  let actions = map ($ messages) (getEventHandlers @'MessageDeleteBulkEvt eh)+  pure $ rawActions <> actions handleEvent' eh evt@(MessageReactionAdd reaction) = do-    updateCache evt-    msg <- getMessage (getID reaction)-    user <- getUser (getID reaction)-    chan <- case reaction ^. #guildID of-        Just _ -> do-            chan <- getGuildChannel (coerceSnowflake $ getID @Channel reaction)-            pure (GuildChannel' <$> chan)-        Nothing -> do-            chan <- getDM (coerceSnowflake $ getID @Channel reaction)-            pure (DMChannel' <$> chan)-    let rawActions = map ($ reaction) (getEventHandlers @'RawMessageReactionAddEvt eh)-    let actions = case (msg, user, chan) of-            (Just msg', Just user', Just chan') ->-                map ($ (msg', user', chan', reaction ^. #emoji)) (getEventHandlers @'MessageReactionAddEvt eh)-            _ -> []-    pure $ rawActions <> actions+  updateCache evt+  msg <- getMessage (getID reaction)+  user <- getUser (getID reaction)+  chan <- case reaction ^. #guildID of+    Just _ -> do+      chan <- getGuildChannel (coerceSnowflake $ getID @Channel reaction)+      pure (GuildChannel' <$> chan)+    Nothing -> do+      chan <- getDM (coerceSnowflake $ getID @Channel reaction)+      pure (DMChannel' <$> chan)+  let rawActions = map ($ reaction) (getEventHandlers @'RawMessageReactionAddEvt eh)+  let actions = case (msg, user, chan) of+        (Just msg', Just user', Just chan') ->+          map ($ (msg', user', chan', reaction ^. #emoji)) (getEventHandlers @'MessageReactionAddEvt eh)+        _ -> []+  pure $ rawActions <> actions handleEvent' eh evt@(MessageReactionRemove reaction) = do-    msg <- getMessage (getID reaction)-    updateCache evt-    user <- getUser (getID reaction)-    chan <- case reaction ^. #guildID of-        Just _ -> do-            chan <- getGuildChannel (coerceSnowflake $ getID @Channel reaction)-            pure (GuildChannel' <$> chan)-        Nothing -> do-            chan <- getDM (coerceSnowflake $ getID @Channel reaction)-            pure (DMChannel' <$> chan)-    let rawActions = map ($ reaction) (getEventHandlers @'RawMessageReactionRemoveEvt eh)-    let actions = case (msg, user, chan) of-            (Just msg', Just user', Just chan') ->-                map ($ (msg', user', chan', reaction ^. #emoji)) (getEventHandlers @'MessageReactionRemoveEvt eh)-            _ -> []-    pure $ rawActions <> actions-handleEvent' eh evt@(MessageReactionRemoveAll MessageReactionRemoveAllData{messageID}) = do-    msg <- getMessage messageID-    updateCache evt-    let rawActions = map ($ messageID) (getEventHandlers @'RawMessageReactionRemoveAllEvt eh)-    let actions = case msg of-            Just msg' ->-                map ($ msg') (getEventHandlers @'MessageReactionRemoveAllEvt eh)-            _ -> []-    pure $ rawActions <> actions-handleEvent' eh evt@(PresenceUpdate PresenceUpdateData{userID, presence = Presence{guildID}}) = do-    Just oldGuild <- getGuild guildID-    Just oldMember <- pure $ oldGuild ^. #members . at (coerceSnowflake userID)-    updateCache evt-    Just newGuild <- getGuild guildID-    Just newMember <- pure $ newGuild ^. #members . at (coerceSnowflake userID)-    let oldUser :: User = upcast oldMember-        newUser :: User = upcast newMember-        userUpdates =-            if oldUser /= newUser-                then map ($ (oldUser, newUser)) (getEventHandlers @'UserUpdateEvt eh)-                else mempty-    pure $ userUpdates <> map ($ (oldMember, newMember)) (getEventHandlers @'GuildMemberUpdateEvt eh)-handleEvent' eh (TypingStart TypingStartData{channelID, guildID, userID, timestamp = UnixTimestamp timestamp}) =-    case guildID of-        Just gid -> do-            Just guild <- getGuild gid-            Just chan <- pure $ GuildChannel' <$> guild ^. #channels . at (coerceSnowflake channelID)-            pure $ map ($ (chan, userID, timestamp)) (getEventHandlers @'TypingStartEvt eh)-        Nothing -> do-            Just chan <- DMChannel' <<$>> getDM (coerceSnowflake channelID)-            pure $ map ($ (chan, userID, timestamp)) (getEventHandlers @'TypingStartEvt eh)+  msg <- getMessage (getID reaction)+  updateCache evt+  user <- getUser (getID reaction)+  chan <- case reaction ^. #guildID of+    Just _ -> do+      chan <- getGuildChannel (coerceSnowflake $ getID @Channel reaction)+      pure (GuildChannel' <$> chan)+    Nothing -> do+      chan <- getDM (coerceSnowflake $ getID @Channel reaction)+      pure (DMChannel' <$> chan)+  let rawActions = map ($ reaction) (getEventHandlers @'RawMessageReactionRemoveEvt eh)+  let actions = case (msg, user, chan) of+        (Just msg', Just user', Just chan') ->+          map ($ (msg', user', chan', reaction ^. #emoji)) (getEventHandlers @'MessageReactionRemoveEvt eh)+        _ -> []+  pure $ rawActions <> actions+handleEvent' eh evt@(MessageReactionRemoveAll MessageReactionRemoveAllData {messageID}) = do+  msg <- getMessage messageID+  updateCache evt+  let rawActions = map ($ messageID) (getEventHandlers @'RawMessageReactionRemoveAllEvt eh)+  let actions = case msg of+        Just msg' ->+          map ($ msg') (getEventHandlers @'MessageReactionRemoveAllEvt eh)+        _ -> []+  pure $ rawActions <> actions+handleEvent' eh evt@(PresenceUpdate PresenceUpdateData {userID, presence = Presence {guildID}}) = do+  Just oldGuild <- getGuild guildID+  Just oldMember <- pure $ oldGuild ^. #members . at (coerceSnowflake userID)+  updateCache evt+  Just newGuild <- getGuild guildID+  Just newMember <- pure $ newGuild ^. #members . at (coerceSnowflake userID)+  let oldUser :: User = upcast oldMember+      newUser :: User = upcast newMember+      userUpdates =+        if oldUser /= newUser+          then map ($ (oldUser, newUser)) (getEventHandlers @'UserUpdateEvt eh)+          else mempty+  pure $ userUpdates <> map ($ (oldMember, newMember)) (getEventHandlers @'GuildMemberUpdateEvt eh)+handleEvent' eh (TypingStart TypingStartData {channelID, guildID, userID, timestamp = UnixTimestamp timestamp}) =+  case guildID of+    Just gid -> do+      Just guild <- getGuild gid+      Just chan <- pure $ GuildChannel' <$> guild ^. #channels . at (coerceSnowflake channelID)+      pure $ map ($ (chan, userID, timestamp)) (getEventHandlers @'TypingStartEvt eh)+    Nothing -> do+      Just chan <- DMChannel' <<$>> getDM (coerceSnowflake channelID)+      pure $ map ($ (chan, userID, timestamp)) (getEventHandlers @'TypingStartEvt eh) handleEvent' eh evt@(UserUpdate _) = do-    Just oldUser <- getBotUser-    updateCache evt-    Just newUser <- getBotUser-    pure $ map ($ (oldUser, newUser)) (getEventHandlers @'UserUpdateEvt eh)-handleEvent' eh evt@(VoiceStateUpdate newVoiceState@V.VoiceState{guildID = Just guildID}) = do-    oldVoiceState <- ((find ((== V.sessionID newVoiceState) . V.sessionID) . voiceStates) =<<) <$> getGuild guildID-    updateCache evt-    pure $ map ($ (oldVoiceState, newVoiceState)) (getEventHandlers @'VoiceStateUpdateEvt eh)+  Just oldUser <- getBotUser+  updateCache evt+  Just newUser <- getBotUser+  pure $ map ($ (oldUser, newUser)) (getEventHandlers @'UserUpdateEvt eh)+handleEvent' eh evt@(VoiceStateUpdate newVoiceState@V.VoiceState {guildID = Just guildID}) = do+  oldVoiceState <- ((find ((== V.sessionID newVoiceState) . V.sessionID) . voiceStates) =<<) <$> getGuild guildID+  updateCache evt+  pure $ map ($ (oldVoiceState, newVoiceState)) (getEventHandlers @'VoiceStateUpdateEvt eh) handleEvent' eh evt@(InteractionCreate interaction) = do-    updateCache evt-    pure $ map ($ interaction) (getEventHandlers @'InteractionEvt eh)+  updateCache evt+  pure $ map ($ interaction) (getEventHandlers @'InteractionEvt eh) handleEvent' _ e = fail $ "Unhandled event: " <> show e  updateCache :: P.Members '[CacheEff, P.Fail] r => DispatchData -> P.Sem r ()-updateCache (Ready ReadyData{user, guilds}) = do-    setBotUser user-    for_ (map getID guilds) setUnavailableGuild+updateCache (Ready ReadyData {user, guilds}) = do+  setBotUser user+  for_ (map getID guilds) setUnavailableGuild updateCache Resumed = pure () updateCache (ChannelCreate (DMChannel' chan)) =-    setDM chan+  setDM chan updateCache (ChannelCreate (GuildChannel' chan)) =-    updateGuild (getID chan) (#channels %~ SM.insert chan)+  updateGuild (getID chan) (#channels %~ SM.insert chan) updateCache (ChannelUpdate (DMChannel' chan)) =-    updateDM (getID chan) (update chan)+  updateDM (getID chan) (update chan) updateCache (ChannelUpdate (GuildChannel' chan)) =-    updateGuild (getID chan) (#channels . ix (getID chan) %~ update chan)+  updateGuild (getID chan) (#channels . ix (getID chan) %~ update chan) updateCache (ChannelDelete (DMChannel' chan)) =-    delDM (getID chan)+  delDM (getID chan) updateCache (ChannelDelete (GuildChannel' chan)) =-    updateGuild (getID chan) (#channels %~ sans (getID chan))+  updateGuild (getID chan) (#channels %~ sans (getID chan)) updateCache (GuildCreate guild) = do-    isNew <- isUnavailableGuild (getID guild)-    when isNew $ delUnavailableGuild (getID guild)-    setGuild guild-    for_ (SM.fromList (guild ^.. #members . traverse . super)) setUser+  isNew <- isUnavailableGuild (getID guild)+  when isNew $ delUnavailableGuild (getID guild)+  setGuild guild+  for_ (SM.fromList (guild ^.. #members . traverse . super)) setUser updateCache (GuildUpdate guild) =-    updateGuild (getID guild) (update guild)-updateCache (GuildDelete UnavailableGuild{id, unavailable}) =-    if unavailable-        then setUnavailableGuild id-        else delGuild id-updateCache (GuildEmojisUpdate GuildEmojisUpdateData{guildID, emojis}) =-    updateGuild guildID (#emojis .~ SM.fromList emojis)-updateCache (GuildMemberAdd member) = do-    setUser (member ^. super)-    updateGuild (getID member) (#members . at (getID member) ?~ member)-updateCache (GuildMemberRemove GuildMemberRemoveData{guildID, user}) =-    updateGuild guildID (#members %~ sans (getID user))-updateCache (GuildMemberUpdate GuildMemberUpdateData{guildID, roles = AesonVector roles, user, nick}) = do-    setUser user-    updateGuild guildID (#members . ix (getID user) %~ (#roles .~ roles) . (#nick .~ nick))-updateCache (GuildMembersChunk GuildMembersChunkData{members}) =-    traverse_ (updateCache . GuildMemberAdd) members-updateCache (GuildRoleCreate GuildRoleData{guildID, role}) =-    updateGuild guildID (#roles %~ SM.insert role)-updateCache (GuildRoleUpdate GuildRoleData{guildID, role}) =-    updateGuild guildID (#roles %~ SM.insert role)-updateCache (GuildRoleDelete GuildRoleDeleteData{guildID, roleID}) =-    updateGuild guildID (#roles %~ sans roleID)+  updateGuild (getID guild) (update guild)+updateCache (GuildDelete UnavailableGuild {id, unavailable}) =+  if unavailable+    then setUnavailableGuild id+    else delGuild id+updateCache (GuildEmojisUpdate GuildEmojisUpdateData {guildID, emojis}) =+  updateGuild guildID (#emojis .~ SM.fromList emojis)+updateCache (GuildMemberAdd gid member) = do+  setUser (member ^. super)+  updateGuild gid (#members . at (getID member) ?~ member)+updateCache (GuildMemberRemove GuildMemberRemoveData {guildID, user}) =+  updateGuild guildID (#members %~ sans (getID user))+updateCache (GuildMemberUpdate GuildMemberUpdateData {guildID, roles = AesonVector roles, user, nick}) = do+  setUser user+  updateGuild guildID (#members . ix (getID user) %~ (#roles .~ roles) . (#nick .~ nick))+updateCache (GuildMembersChunk GuildMembersChunkData {guildID, members}) =+  traverse_ (updateCache . GuildMemberAdd guildID) members+updateCache (GuildRoleCreate GuildRoleData {guildID, role}) =+  updateGuild guildID (#roles %~ SM.insert role)+updateCache (GuildRoleUpdate GuildRoleData {guildID, role}) =+  updateGuild guildID (#roles %~ SM.insert role)+updateCache (GuildRoleDelete GuildRoleDeleteData {guildID, roleID}) =+  updateGuild guildID (#roles %~ sans roleID) updateCache (MessageCreate !msg !_ !_) =-    setMessage msg-    -- I think it's for the best not to cache things here, instead the end user-    -- can just cache manually which users and members they want+  setMessage msg+-- I think it's for the best not to cache things here, instead the end user+-- can just cache manually which users and members they want updateCache (MessageUpdate msg !_ !_) =-    updateMessage (getID msg) (update msg)-updateCache (MessageDelete MessageDeleteData{id}) = delMessage id-updateCache (MessageDeleteBulk MessageDeleteBulkData{ids}) =-    for_ ids delMessage+  updateMessage (getID msg) (update msg)+updateCache (MessageDelete MessageDeleteData {id}) = delMessage id+updateCache (MessageDeleteBulk MessageDeleteBulkData {ids}) =+  for_ ids delMessage updateCache (MessageReactionAdd reaction) = do-    isMe <- (\u -> Just (getID @User reaction) == (getID @User <$> u)) <$> getBotUser-    updateMessage-        (getID reaction)-        ( \m ->-            case m ^. #reactions & filter ((== (reaction ^. #emoji)) . (^. #emoji)) of-                [] -> m & #reactions <>~ [Reaction 1 isMe (reaction ^. #emoji)]-                _ ->-                    m & #reactions . traverse . filtered ((== (reaction ^. #emoji)) . (^. #emoji))-                        %~ (#count +~ 1) . (#me ||~ isMe)-        )+  isMe <- (\u -> Just (getID @User reaction) == (getID @User <$> u)) <$> getBotUser+  updateMessage+    (getID reaction)+    ( \m ->+        case m ^. #reactions & filter ((== (reaction ^. #emoji)) . (^. #emoji)) of+          [] -> m & #reactions <>~ [Reaction 1 isMe (reaction ^. #emoji)]+          _ ->+            m & #reactions . traverse . filtered ((== (reaction ^. #emoji)) . (^. #emoji))+              %~ (#count +~ 1) . (#me ||~ isMe)+    ) updateCache (MessageReactionRemove reaction) = do-    isMe <- (\u -> Just (getID @User reaction) == (getID @User <$> u)) <$> getBotUser-    updateMessage-        (getID reaction)-        ( \m ->-            m-                & #reactions . traverse . filtered ((== (reaction ^. #emoji)) . (^. #emoji))-                    %~ (#count -~ 1) . (#me &&~ not isMe)-                & #reactions %~ filter (\r -> r ^. #count /= 0)-        )-updateCache (MessageReactionRemoveAll MessageReactionRemoveAllData{messageID}) =-    updateMessage messageID (#reactions .~ mempty)-updateCache (PresenceUpdate PresenceUpdateData{userID, presence}) =-    updateGuild (getID presence) (#presences . at userID ?~ presence)+  isMe <- (\u -> Just (getID @User reaction) == (getID @User <$> u)) <$> getBotUser+  updateMessage+    (getID reaction)+    ( \m ->+        m+          & #reactions . traverse . filtered ((== (reaction ^. #emoji)) . (^. #emoji))+            %~ (#count -~ 1) . (#me &&~ not isMe)+          & #reactions %~ filter (\r -> r ^. #count /= 0)+    )+updateCache (MessageReactionRemoveAll MessageReactionRemoveAllData {messageID}) =+  updateMessage messageID (#reactions .~ mempty)+updateCache (PresenceUpdate PresenceUpdateData {userID, presence}) =+  updateGuild (getID presence) (#presences . at userID ?~ presence) updateCache (UserUpdate user) = setBotUser user -- we don't handle group channels currently updateCache (ChannelCreate (GroupChannel' _)) = pure ()@@ -744,16 +760,16 @@ updateCache (WebhooksUpdate _) = pure () updateCache (InviteCreate _) = pure () updateCache (InviteDelete _) = pure ()-updateCache (VoiceStateUpdate voiceState@V.VoiceState{guildID = Just guildID}) =-    updateGuild guildID (#voiceStates %~ updateVoiceStates)+updateCache (VoiceStateUpdate voiceState@V.VoiceState {guildID = Just guildID}) =+  updateGuild guildID (#voiceStates %~ updateVoiceStates)   where     updateVoiceStates [] = [voiceState]     updateVoiceStates (x : xs)-        | V.sessionID x == V.sessionID voiceState = voiceState : xs-        | otherwise = x : updateVoiceStates xs+      | V.sessionID x == V.sessionID voiceState = voiceState : xs+      | otherwise = x : updateVoiceStates xs  -- we don't handle voice server update and direct voice connections currently-updateCache (VoiceStateUpdate V.VoiceState{guildID = Nothing}) = pure ()+updateCache (VoiceStateUpdate V.VoiceState {guildID = Nothing}) = pure () updateCache (VoiceServerUpdate _) = pure () -- we don't update the cache from interactions -- TODO: should we?
Calamity/Client/Types.hs view
@@ -19,16 +19,19 @@ import Calamity.Cache.Eff import Calamity.Gateway.DispatchEvents (CalamityEvent (..), InviteCreateData, InviteDeleteData, ReactionEvtData, ReadyData) import Calamity.Gateway.Types (ControlMessage)+import Calamity.HTTP.Internal.Ratelimit import Calamity.HTTP.Internal.Types import Calamity.Metrics.Eff import Calamity.Types.LogEff import Calamity.Types.Model.Channel import Calamity.Types.Model.Channel.UpdatedMessage import Calamity.Types.Model.Guild+import Calamity.Types.Model.Interaction (Interaction) import Calamity.Types.Model.User import Calamity.Types.Model.Voice import Calamity.Types.Snowflake import Calamity.Types.Token+import Calamity.Types.TokenEff import Control.Concurrent.Async import Control.Concurrent.Chan.Unagi import Control.Concurrent.MVar@@ -36,24 +39,23 @@ import Data.Default.Class import Data.Dynamic import Data.IORef+import Data.Kind (Type) import Data.Maybe import Data.Time import Data.TypeRepMap (TypeRepMap, WrapTypeable (..)) import qualified Data.TypeRepMap as TM import Data.Typeable+import Data.Void (Void)+import qualified Df1+import qualified Di.Core as DC import GHC.Exts (fromList) import GHC.Generics import qualified Polysemy as P import qualified Polysemy.Async as P import qualified Polysemy.AtomicState as P import qualified Polysemy.Reader as P-import qualified Df1-import qualified Di.Core as DC import TextShow import qualified TextShow.Generic as TSG-import Data.Kind (Type)-import Data.Void (Void)-import Calamity.Types.Model.Interaction (Interaction)  data Client = Client   { shards :: TVar [(InChan ControlMessage, Async (Maybe ()))]@@ -73,6 +75,8 @@       '[ LogEff        , MetricEff        , CacheEff+       , RatelimitEff+       , TokenEff        , P.Reader Client        , P.AtomicState EventHandlers        , P.Embed IO@@ -83,7 +87,7 @@   )  -- | A concrete effect stack used inside the bot-type SetupEff r = (P.Reader Client ': P.AtomicState EventHandlers ': P.Async ': r)+type SetupEff r = (RatelimitEff ': TokenEff ': P.Reader Client ': P.AtomicState EventHandlers ': P.Async ': r)  {- | Some constraints that 'Calamity.Client.Client.react' needs to work. Don't  worry about these since they are satisfied for any type @s@ can be@@ -94,7 +98,7 @@   )  newtype StartupError = StartupError String-  deriving (Show)+  deriving stock (Show)  -- | A Data Kind used to fire custom events data EventType@@ -240,7 +244,7 @@ newtype CustomEHTypeStorage (a :: Type) = CustomEHTypeStorage   { unwrapCustomEHTypeStorage :: [EventHandlerWithID (a -> IO ())]   }-  deriving newtype ( Monoid, Semigroup )+  deriving newtype (Monoid, Semigroup)  type family EHStorageType (t :: EventType) where   EHStorageType ( 'CustomEvt _) = TypeRepMap CustomEHTypeStorage@@ -289,7 +293,7 @@         , WrapTypeable $ EH @'TypingStartEvt []         , WrapTypeable $ EH @'UserUpdateEvt []         , WrapTypeable $ EH @'InteractionEvt []-        , WrapTypeable $ EH @('CustomEvt Void) TM.empty+        , WrapTypeable $ EH @( 'CustomEvt Void) TM.empty         ]  instance Semigroup EventHandlers where@@ -316,7 +320,7 @@ class InsertEventHandler' (flag :: Bool) a where   makeEventHandlers' :: Proxy flag -> Proxy a -> Integer -> StoredEHType a -> EventHandlers -instance forall (x :: Type). (Typeable (EHType ('CustomEvt x))) => InsertEventHandler' 'True ( 'CustomEvt x) where+instance forall (x :: Type). (Typeable (EHType ( 'CustomEvt x))) => InsertEventHandler' 'True ( 'CustomEvt x) where   makeEventHandlers' _ _ id' handler =     EventHandlers . TM.one $       EH @( 'CustomEvt Void)
Calamity/Gateway/DispatchEvents.hs view
@@ -49,7 +49,7 @@     | GuildBanRemove !BanData     | GuildEmojisUpdate !GuildEmojisUpdateData     | GuildIntegrationsUpdate !GuildIntegrationsUpdateData-    | GuildMemberAdd !Member+    | GuildMemberAdd !(Snowflake Guild) !Member     | GuildMemberRemove !GuildMemberRemoveData     | GuildMemberUpdate !GuildMemberUpdateData     | GuildMembersChunk !GuildMembersChunkData@@ -131,7 +131,7 @@          members' <- do             members' <- v .: "members"-            traverse (\m -> parseJSON $ Object (m <> "guild_id" .= guildID)) members'+            traverse (parseJSON . Object) members'          pure $ GuildMembersChunkData guildID members' 
Calamity/Gateway/Intents.hs view
@@ -48,6 +48,7 @@     , ("intentDirectMessages", 1 `shiftL` 12)     , ("intentDirectMessageReactions", 1 `shiftL` 13)     , ("intentDirectMessageTyping", 1 `shiftL` 14)+    , ("intentGuildScheduledEvents", 1 `shiftL` 16)     ]  ) 
Calamity/Gateway/Shard.hs view
@@ -184,6 +184,7 @@           whenJust reason (\r -> error [fmt|Shard closed with reason: {r}|])           P.embed . atomically $ writeTBMQueue outqueue (Control c)         Right msg' -> do+          -- debug [fmt|Got msg: {msg'}|]           let decoded = A.eitherDecode msg'           r <- case decoded of             Right a ->
Calamity/Gateway/Types.hs view
@@ -104,7 +104,9 @@ parseDispatchData GUILD_BAN_REMOVE data'            = GuildBanRemove <$> parseJSON data' parseDispatchData GUILD_EMOJIS_UPDATE data'         = GuildEmojisUpdate <$> parseJSON data' parseDispatchData GUILD_INTEGRATIONS_UPDATE data'   = GuildIntegrationsUpdate <$> parseJSON data'-parseDispatchData GUILD_MEMBER_ADD data'            = GuildMemberAdd <$> parseJSON data'+parseDispatchData GUILD_MEMBER_ADD data'            = do+  guildID <- withObject "GuildMemberAdd.guild_id" (.: "guild_id") data'+  GuildMemberAdd guildID <$> parseJSON data' parseDispatchData GUILD_MEMBER_REMOVE data'         = GuildMemberRemove <$> parseJSON data' parseDispatchData GUILD_MEMBER_UPDATE data'         = GuildMemberUpdate <$> parseJSON data' parseDispatchData GUILD_MEMBERS_CHUNK data'         = GuildMembersChunk <$> parseJSON data'@@ -118,8 +120,7 @@   let member = parseMaybe (withObject "MessageCreate.member" $ \o -> do                                          userObject :: Object <- o .: "author"                                          memberObject :: Object <- o .: "member"-                                         guildID :: String <- o .: "guild_id"-                                         parseJSON $ Object (memberObject <> "user" .= userObject <> "guild_id" .= guildID)) data'+                                         parseJSON $ Object (memberObject <> "user" .= userObject)) data'    let user = parseMaybe parseJSON =<< data' ^? _Object . ix "author"   pure $ MessageCreate message user member@@ -128,8 +129,7 @@   let member = parseMaybe (withObject "MessageCreate.member" $ \o -> do                                          userObject :: Object <- o .: "author"                                          memberObject :: Object <- o .: "member"-                                         guildID :: String <- o .: "guild_id"-                                         parseJSON $ Object (memberObject <> "user" .= userObject <> "guild_id" .= guildID)) data'+                                         parseJSON $ Object (memberObject <> "user" .= userObject)) data'   let user = parseMaybe parseJSON =<< data' ^? _Object . ix "author"   pure $ MessageUpdate message user member parseDispatchData MESSAGE_DELETE data'              = MessageDelete <$> parseJSON data'@@ -143,6 +143,7 @@ parseDispatchData VOICE_STATE_UPDATE data'          = VoiceStateUpdate <$> parseJSON data' parseDispatchData VOICE_SERVER_UPDATE data'         = VoiceServerUpdate <$> parseJSON data' parseDispatchData WEBHOOKS_UPDATE data'             = WebhooksUpdate <$> parseJSON data'+parseDispatchData INTERACTION_CREATE data'          = InteractionCreate <$> parseJSON data'  data SentDiscordMessage   = StatusUpdate StatusUpdateData@@ -216,6 +217,7 @@   | VOICE_STATE_UPDATE   | VOICE_SERVER_UPDATE   | WEBHOOKS_UPDATE+  | INTERACTION_CREATE   deriving ( Show, Eq, Enum, Generic )   deriving anyclass ( ToJSON, FromJSON ) 
Calamity/HTTP.hs view
@@ -1,31 +1,37 @@ -- | Combined http request stuff-module Calamity.HTTP-    ( invoke-    , module Calamity.HTTP.AuditLog-    , module Calamity.HTTP.Channel-    , module Calamity.HTTP.Emoji-    , module Calamity.HTTP.Guild-    , module Calamity.HTTP.Invite-    , module Calamity.HTTP.MiscRoutes-    , module Calamity.HTTP.User-    , module Calamity.HTTP.Reason-    , module Calamity.HTTP.Internal.Types-    , module Calamity.HTTP.Webhook-    -- * HTTP-    -- $httpDocs-    ) where+module Calamity.HTTP (+  invoke,+  module Calamity.HTTP.AuditLog,+  module Calamity.HTTP.Channel,+  module Calamity.HTTP.Emoji,+  module Calamity.HTTP.Guild,+  module Calamity.HTTP.Invite,+  module Calamity.HTTP.Interaction,+  module Calamity.HTTP.MiscRoutes,+  module Calamity.HTTP.User,+  module Calamity.HTTP.Reason,+  module Calamity.HTTP.Webhook,+  module Calamity.HTTP.Internal.Types,+  RatelimitEff (..),+  TokenEff (..),+  -- * HTTP+  -- $httpDocs+) where -import           Calamity.HTTP.AuditLog-import           Calamity.HTTP.Channel-import           Calamity.HTTP.Emoji-import           Calamity.HTTP.Guild-import           Calamity.HTTP.Internal.Request ( invoke )-import           Calamity.HTTP.Internal.Types   ( RestError )-import           Calamity.HTTP.Invite-import           Calamity.HTTP.MiscRoutes-import           Calamity.HTTP.Reason-import           Calamity.HTTP.User-import           Calamity.HTTP.Webhook+import Calamity.HTTP.AuditLog+import Calamity.HTTP.Channel+import Calamity.HTTP.Emoji+import Calamity.HTTP.Guild+import Calamity.HTTP.Interaction+import Calamity.HTTP.Internal.Ratelimit (RatelimitEff (..))+import Calamity.HTTP.Internal.Request (invoke)+import Calamity.HTTP.Internal.Types (RestError)+import Calamity.HTTP.Invite+import Calamity.HTTP.MiscRoutes+import Calamity.HTTP.Reason+import Calamity.HTTP.User+import Calamity.HTTP.Webhook+import Calamity.Types.TokenEff (TokenEff (..))  -- $httpDocs --
Calamity/HTTP/Channel.hs view
@@ -1,12 +1,14 @@ -- | Channel endpoints module Calamity.HTTP.Channel (   ChannelRequest (..),+  CreateMessageAttachment (..),   CreateMessageOptions (..),   EditMessageData (..),   editMessageContent,-  editMessageEmbed,+  editMessageEmbeds,   editMessageFlags,   editMessageAllowedMentions,+  editMessageComponents,   ChannelUpdate (..),   AllowedMentionType (..),   AllowedMentions (..),@@ -21,7 +23,10 @@ import Calamity.HTTP.Internal.Route import Calamity.Internal.AesonThings import Calamity.Types.Model.Channel-import Calamity.Types.Model.Guild+import Calamity.Types.Model.Guild.Emoji (RawEmoji (..))+import Calamity.Types.Model.Guild.Invite (Invite)+import Calamity.Types.Model.Guild.Overwrite (Overwrite)+import Calamity.Types.Model.Guild.Role (Role) import Calamity.Types.Model.User import Calamity.Types.Snowflake import Control.Lens hiding ((.=))@@ -29,27 +34,57 @@ import qualified Data.Aeson.KeyMap as K import Data.ByteString.Lazy (ByteString) import Data.Default.Class-import Data.Generics.Product.Subtype (upcast)+import Data.Maybe (fromMaybe) import Data.Text (Text)-import qualified Data.Text as S+import qualified Data.Text as T import Data.Word import GHC.Generics import Network.HTTP.Client.MultipartFormData import Network.HTTP.Req import Network.Mime+import PyF import TextShow +data CreateMessageAttachment = CreateMessageAttachment+  { filename :: Text+  , description :: Maybe Text+  , content :: ByteString+  }+  deriving (Show, Generic)+ data CreateMessageOptions = CreateMessageOptions   { content :: Maybe Text   , nonce :: Maybe Text   , tts :: Maybe Bool-  , file :: Maybe (Text, ByteString)-  , embed :: Maybe Embed+  , attachments :: Maybe [CreateMessageAttachment]+  , embeds :: Maybe [Embed]   , allowedMentions :: Maybe AllowedMentions   , messageReference :: Maybe MessageReference+  , components :: Maybe [Component]   }   deriving (Show, Generic, Default) +data CreateMessageAttachmentJson = CreateMessageAttachmentJson+  { id :: Int+  , filename :: Text+  , description :: Maybe Text+  }+  deriving (Show, Generic)+  deriving (ToJSON) via CalamityJSON CreateMessageAttachmentJson++data CreateMessageJson = CreateMessageJson+  { content :: Maybe Text+  , nonce :: Maybe Text+  , tts :: Maybe Bool+  , embeds :: Maybe [Embed]+  , allowedMentions :: Maybe AllowedMentions+  , messageReference :: Maybe MessageReference+  , components :: Maybe [Component]+  , attachments :: Maybe [CreateMessageAttachmentJson]+  }+  deriving (Show, Generic)+  deriving (ToJSON) via CalamityJSON CreateMessageJson+ data AllowedMentionType   = AllowedMentionRoles   | AllowedMentionUsers@@ -80,17 +115,6 @@ instance Monoid AllowedMentions where   mempty = def -data CreateMessageJson = CreateMessageJson-  { content :: Maybe Text-  , nonce :: Maybe Text-  , tts :: Maybe Bool-  , embed :: Maybe Embed-  , allowedMentions :: Maybe AllowedMentions-  , messageReference :: Maybe MessageReference-  }-  deriving (Show, Generic)-  deriving (ToJSON) via CalamityJSON CreateMessageJson- {- | Parameters to the Edit Message endpoint.   Use the provided methods (@editMessageX@) to create a value with the@@ -108,8 +132,8 @@ editMessageContent :: Maybe Text -> EditMessageData editMessageContent v = EditMessageData $ K.fromList [("content", toJSON v)] -editMessageEmbed :: Maybe Embed -> EditMessageData-editMessageEmbed v = EditMessageData $ K.fromList [("embed", toJSON v)]+editMessageEmbeds :: [Embed] -> EditMessageData+editMessageEmbeds v = EditMessageData $ K.fromList [("embeds", toJSON v)]  editMessageFlags :: Maybe Word64 -> EditMessageData editMessageFlags v = EditMessageData $ K.fromList [("flags", toJSON v)]@@ -117,6 +141,9 @@ editMessageAllowedMentions :: Maybe AllowedMentions -> EditMessageData editMessageAllowedMentions v = EditMessageData $ K.fromList [("allowed_mentions", toJSON v)] +editMessageComponents :: [Component] -> EditMessageData+editMessageComponents v = EditMessageData $ K.fromList [("components", toJSON v)]+ data ChannelUpdate = ChannelUpdate   { name :: Maybe Text   , position :: Maybe Int@@ -306,17 +333,28 @@     baseRoute cid // S "recipients" // ID @User       & giveID uid       & buildRoute--  action (CreateMessage _ o@CreateMessageOptions{file = Nothing}) =-    postWith'-      (ReqBodyJson . upcast @CreateMessageJson $ o)-  action (CreateMessage _ cm@CreateMessageOptions{file = Just f}) = \u o -> do-    let filePart =-          (partLBS @IO "file" (snd f))-            { partFilename = Just (S.unpack $ fst f)-            , partContentType = Just (defaultMimeLookup $ fst f)+  action (CreateMessage _ cm) = \u o -> do+    let filePart CreateMessageAttachment {filename, content} n =+          (partLBS @IO [fmt|files[{n}]|] content)+            { partFilename = Just (T.unpack filename)+            , partContentType = Just (defaultMimeLookup filename)             }-    body <- reqBodyMultipart [filePart, partLBS "payload_json" (encode . upcast @CreateMessageJson $ cm)]+        attachmentPart CreateMessageAttachment {filename, description} n =+          CreateMessageAttachmentJson n filename description+        files = zipWith filePart (fromMaybe [] $ cm ^. #attachments) [(0 :: Int) ..]+        attachments = (\a -> zipWith attachmentPart a [0 ..]) <$> cm ^. #attachments+        jsonBody =+          CreateMessageJson+            { content = cm ^. #content+            , nonce = cm ^. #nonce+            , tts = cm ^. #tts+            , embeds = cm ^. #embeds+            , allowedMentions = cm ^. #allowedMentions+            , messageReference = cm ^. #messageReference+            , components = cm ^. #components+            , attachments = attachments+            }+    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonBody) : files)     postWith' body u o   action (CrosspostMessage _ _) = postEmpty   action (GetChannel _) = getWith@@ -330,10 +368,10 @@     getWithP ("after" =: a <> "limit" =:? (l ^? _Just . #limit))   action (GetChannelMessages _ Nothing l) = getWithP ("limit" =:? (l ^? _Just . #limit))   action (GetMessage _ _) = getWith-  action CreateReaction{} = putEmpty-  action DeleteOwnReaction{} = deleteWith-  action DeleteUserReaction{} = deleteWith-  action (GetReactions _ _ _ GetReactionsOptions{before, after, limit}) =+  action CreateReaction {} = putEmpty+  action DeleteOwnReaction {} = deleteWith+  action DeleteUserReaction {} = deleteWith+  action (GetReactions _ _ _ GetReactionsOptions {before, after, limit}) =     getWithP       ( "before" =:? (fromSnowflake <$> before)           <> "after" =:? (fromSnowflake <$> after)
Calamity/HTTP/Guild.hs view
@@ -101,6 +101,12 @@   }   deriving (Show, Generic, Default) +data SearchMembersOptions = SearchMembersOptions+  { limit :: Maybe Integer+  , query :: Text+  }+  deriving (Show, Generic)+ data AddGuildMemberData = AddGuildMemberData   { accessToken :: Text   , nick :: Maybe Text@@ -140,6 +146,13 @@ modifyGuildMemberChannelID :: Maybe (Snowflake VoiceChannel) -> ModifyGuildMemberData modifyGuildMemberChannelID v = ModifyGuildMemberData $ K.fromList [("channel_id", toJSON v)] +data GetGuildBansOptions = GetGuildBansOptions+  { limit :: Maybe Int+  , before :: Maybe Int+  , after :: Maybe Int+  }+  deriving (Show, Generic, Default)+ data CreateGuildBanData = CreateGuildBanData   { deleteMessageDays :: Maybe Integer   , reason :: Maybe Text@@ -192,13 +205,14 @@   ModifyGuildChannelPositions :: HasID Guild g => g -> [ChannelPosition] -> GuildRequest ()   GetGuildMember :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest Member   ListGuildMembers :: HasID Guild g => g -> ListMembersOptions -> GuildRequest [Member]+  SearchGuildMembers :: HasID Guild g => g -> SearchMembersOptions -> GuildRequest [Member]   AddGuildMember :: (HasID Guild g, HasID User u) => g -> u -> AddGuildMemberData -> GuildRequest (Maybe Member)   ModifyGuildMember :: (HasID Guild g, HasID User u) => g -> u -> ModifyGuildMemberData -> GuildRequest ()   ModifyCurrentUserNick :: HasID Guild g => g -> Maybe Text -> GuildRequest ()   AddGuildMemberRole :: (HasID Guild g, HasID User u, HasID Role r) => g -> u -> r -> GuildRequest ()   RemoveGuildMemberRole :: (HasID Guild g, HasID User u, HasID Role r) => g -> u -> r -> GuildRequest ()   RemoveGuildMember :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest ()-  GetGuildBans :: HasID Guild g => g -> GuildRequest [BanData]+  GetGuildBans :: HasID Guild g => g -> GetGuildBansOptions -> GuildRequest [BanData]   GetGuildBan :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest BanData   CreateGuildBan :: (HasID Guild g, HasID User u) => g -> u -> CreateGuildBanData -> GuildRequest ()   RemoveGuildBan :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest ()@@ -248,6 +262,9 @@   route (ListGuildMembers (getID -> gid) _) =     baseRoute gid // S "members"       & buildRoute+  route (SearchGuildMembers (getID -> gid) _) =+    baseRoute gid // S "members" // S "search"+      & buildRoute   route (AddGuildMember (getID -> gid) (getID @User -> uid) _) =     baseRoute gid // S "members" // ID @User       & giveID uid@@ -273,7 +290,7 @@     baseRoute gid // S "members" // ID @User       & giveID uid       & buildRoute-  route (GetGuildBans (getID -> gid)) =+  route (GetGuildBans (getID -> gid) _) =     baseRoute gid // S "bans"       & buildRoute   route (GetGuildBan (getID -> gid) (getID @User -> uid)) =@@ -326,20 +343,25 @@   action (CreateGuildChannel _ o) = postWith' (ReqBodyJson o)   action (ModifyGuildChannelPositions _ o) = postWith' (ReqBodyJson o)   action (GetGuildMember _ _) = getWith-  action (ListGuildMembers _ ListMembersOptions{limit, after}) =+  action (ListGuildMembers _ ListMembersOptions {limit, after}) =     getWithP       ("limit" =:? limit <> "after" =:? (fromSnowflake <$> after))+  action (SearchGuildMembers _ SearchMembersOptions {limit, query}) =+    getWithP+      ("limit" =:? limit <> "query" =: query)   action (AddGuildMember _ _ o) = putWith' (ReqBodyJson o)   action (ModifyGuildMember _ _ o) = patchWith' (ReqBodyJson o)   action (ModifyCurrentUserNick _ nick) = patchWith' (ReqBodyJson $ object ["nick" .= nick])-  action AddGuildMemberRole{} = putEmpty-  action RemoveGuildMemberRole{} = deleteWith+  action AddGuildMemberRole {} = putEmpty+  action RemoveGuildMemberRole {} = deleteWith   action (RemoveGuildMember _ _) = deleteWith-  action (GetGuildBans _) = getWith+  action (GetGuildBans _ GetGuildBansOptions {limit, before, after}) =+    getWithP+      ("limit" =:? limit <> "before" =:? before <> "after" =:? after)   action (GetGuildBan _ _) = getWith-  action (CreateGuildBan _ _ CreateGuildBanData{deleteMessageDays, reason}) =+  action (CreateGuildBan _ _ CreateGuildBanData {deleteMessageDays}) =     putEmptyP-      ("delete-message-days" =:? deleteMessageDays <> "reason" =:? reason)+      ("delete_message_days" =:? deleteMessageDays)   action (RemoveGuildBan _ _) = deleteWith   action (GetGuildRoles _) = getWith   action (CreateGuildRole _ o) = postWith' (ReqBodyJson o)@@ -353,6 +375,4 @@    -- this is a bit of a hack   -- TODO: add something to allow for contextual parsing-  modifyResponse (GetGuildMember (getID @Guild -> gid) _) = _Object . at "guild_id" ?~ _String # showt (fromSnowflake gid)-  modifyResponse (ListGuildMembers (getID @Guild -> gid) _) = values . _Object . at "guild_id" ?~ _String # showt (fromSnowflake gid)   modifyResponse _ = Prelude.id
+ Calamity/HTTP/Interaction.hs view
@@ -0,0 +1,427 @@+-- | Interaction endpoints+module Calamity.HTTP.Interaction (+  InteractionRequest (..),+  InteractionCallbackMessageOptions (..),+  InteractionCallbackAutocomplete (..),+  InteractionCallbackAutocompleteChoice (..),+  InteractionCallbackModal (..),+) where++import Calamity.HTTP.Channel (AllowedMentions, CreateMessageAttachment (..))+import Calamity.HTTP.Internal.Request+import Calamity.HTTP.Internal.Route+import Calamity.Internal.AesonThings+import Calamity.Types.Model.Channel.Component (Component, CustomID)+import Calamity.Types.Model.Channel.Embed (Embed)+import Calamity.Types.Model.Channel.Message (Message)+import Calamity.Types.Model.Interaction+import Calamity.Types.Snowflake+import Control.Lens hiding ((.=))+import Data.Aeson+import Data.Bits (shiftL, (.|.))+import Data.Default.Class+import qualified Data.HashMap.Strict as H+import Data.Maybe (fromMaybe)+import Data.Monoid (First (First, getFirst))+import Data.Text (Text)+import qualified Data.Text as T+import GHC.Generics+import Network.HTTP.Client.MultipartFormData+import Network.HTTP.Req+import Network.Mime+import PyF+import TextShow+import qualified TextShow.Generic as TSG++data InteractionCallback = InteractionCallback+  { type_ :: InteractionCallbackType+  , data_ :: Maybe Value+  }+  deriving (Show, Generic)+  deriving (ToJSON) via CalamityJSON InteractionCallback++data InteractionCallbackMessageOptions = InteractionCallbackMessageOptions+  { tts :: Maybe Bool+  , content :: Maybe Text+  , embeds :: Maybe [Embed]+  , allowedMentions :: Maybe AllowedMentions+  , ephemeral :: Maybe Bool+  , suppressEmbeds :: Maybe Bool+  , components :: Maybe [Component]+  , attachments :: Maybe [CreateMessageAttachment]+  }+  deriving (Show, Generic, Default)++data CreateMessageAttachmentJson = CreateMessageAttachmentJson+  { id :: Int+  , filename :: Text+  , description :: Maybe Text+  }+  deriving (Show, Generic)+  deriving (ToJSON) via CalamityJSON CreateMessageAttachmentJson++data CreateResponseMessageJson = CreateResponseMessageJson+  { tts :: Maybe Bool+  , content :: Maybe Text+  , embeds :: Maybe [Embed]+  , allowedMentions :: Maybe AllowedMentions+  , flags :: Maybe Int+  , components :: Maybe [Component]+  , attachments :: Maybe [CreateMessageAttachmentJson]+  }+  deriving (Show, Generic)+  deriving (ToJSON) via CalamityJSON CreateResponseMessageJson++newtype InteractionCallbackAutocomplete = InteractionCallbackAutocomplete+  { choices :: [InteractionCallbackAutocompleteChoice]+  }+  deriving stock (Show, Generic)+  deriving (ToJSON) via CalamityJSON InteractionCallbackAutocomplete++data InteractionCallbackAutocompleteChoice = InteractionCallbackAutocompleteChoice+  { name :: Text+  , nameLocalizations :: H.HashMap Text Text+  , -- | Either text or numeric+    value :: Value+  }+  deriving stock (Show, Generic)+  deriving (ToJSON) via CalamityJSON InteractionCallbackAutocompleteChoice++data InteractionCallbackModal = InteractionCallbackModal+  { customID :: CustomID+  , title :: Text+  , components :: [Component]+  }+  deriving stock (Show, Generic)+  deriving (ToJSON) via CalamityJSON InteractionCallbackModal++data InteractionCallbackType+  = PongType+  | ChannelMessageWithSourceType+  | DeferredChannelMessageWithSourceType+  | DeferredUpdateMessageType+  | UpdateMessageType+  | ApplicationCommandAutocompleteResultType+  | ModalType+  deriving (Show, Generic)+  deriving (TextShow) via TSG.FromGeneric InteractionCallbackType++instance ToJSON InteractionCallbackType where+  toEncoding ty = toEncoding @Int $ case ty of+    PongType -> 1+    ChannelMessageWithSourceType -> 4+    DeferredChannelMessageWithSourceType -> 5+    DeferredUpdateMessageType -> 6+    UpdateMessageType -> 7+    ApplicationCommandAutocompleteResultType -> 8+    ModalType -> 9++data InteractionRequest a where+  CreateResponseMessage ::+    (HasID Interaction i) =>+    i ->+    InteractionToken ->+    InteractionCallbackMessageOptions ->+    InteractionRequest ()+  -- | Ack an interaction and defer the response+  --+  -- This route triggers the 'thinking' message+  CreateResponseDefer ::+    (HasID Interaction i) =>+    i ->+    InteractionToken ->+    -- | Ephemeral+    Bool ->+    InteractionRequest ()+  -- | Ack an interaction and defer the response+  --+  -- This route is only usable by component interactions, and doesn't trigger a+  -- 'thinking' message+  CreateResponseDeferComponent ::+    (HasID Interaction i) =>+    i ->+    InteractionToken ->+    InteractionRequest ()+  CreateResponseUpdate ::+    (HasID Interaction i) =>+    i ->+    InteractionToken ->+    InteractionCallbackMessageOptions ->+    InteractionRequest ()+  CreateResponseAutocomplete ::+    (HasID Interaction i) =>+    i ->+    InteractionToken ->+    InteractionCallbackAutocomplete ->+    InteractionRequest ()+  CreateResponseModal ::+    (HasID Interaction i) =>+    i ->+    InteractionToken ->+    InteractionCallbackModal ->+    InteractionRequest ()+  GetOriginalInteractionResponse ::+    (HasID Application i) =>+    i ->+    InteractionToken ->+    InteractionRequest Message+  EditOriginalInteractionResponse ::+    (HasID Application i) =>+    i ->+    InteractionToken ->+    InteractionCallbackMessageOptions ->+    InteractionRequest Message+  DeleteOriginalInteractionResponse ::+    (HasID Application i) =>+    i ->+    InteractionToken ->+    InteractionRequest ()+  CreateFollowupMessage ::+    (HasID Application i) =>+    i ->+    InteractionToken ->+    InteractionCallbackMessageOptions ->+    InteractionRequest ()+  GetFollowupMessage ::+    (HasID Application i, HasID Message m) =>+    i ->+    m ->+    InteractionToken ->+    InteractionRequest Message+  EditFollowupMessage ::+    (HasID Application i, HasID Message m) =>+    i ->+    m ->+    InteractionToken ->+    InteractionCallbackMessageOptions ->+    InteractionRequest ()+  DeleteFollowupMessage ::+    (HasID Application i, HasID Message m) =>+    i ->+    m ->+    InteractionToken ->+    InteractionRequest ()++baseRoute :: Snowflake Application -> InteractionToken -> RouteBuilder _+baseRoute id (InteractionToken token) =+  mkRouteBuilder // S "webhooks" // ID @Application // S token+    & giveID id++foo :: Maybe a -> Maybe a -> (a -> a -> a) -> Maybe a+foo (Just x) (Just y) f = Just (f x y)+foo x y _ = getFirst $ First x <> First y++instance Request (InteractionRequest a) where+  type Result (InteractionRequest a) = a++  route (CreateResponseDefer (getID @Interaction -> iid) (InteractionToken token) _) =+    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"+      & giveID iid+      & buildRoute+  route (CreateResponseDeferComponent (getID @Interaction -> iid) (InteractionToken token)) =+    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"+      & giveID iid+      & buildRoute+  route (CreateResponseMessage (getID @Interaction -> iid) (InteractionToken token) _) =+    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"+      & giveID iid+      & buildRoute+  route (CreateResponseUpdate (getID @Interaction -> iid) (InteractionToken token) _) =+    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"+      & giveID iid+      & buildRoute+  route (CreateResponseAutocomplete (getID @Interaction -> iid) (InteractionToken token) _) =+    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"+      & giveID iid+      & buildRoute+  route (CreateResponseModal (getID @Interaction -> iid) (InteractionToken token) _) =+    mkRouteBuilder // S "interactions" // ID @Interaction // S token // S "callback"+      & giveID iid+      & buildRoute+  route (GetOriginalInteractionResponse (getID @Application -> aid) token) =+    baseRoute aid token // S "messages" // S "@original" & buildRoute+  route (EditOriginalInteractionResponse (getID @Application -> aid) token _) =+    baseRoute aid token // S "messages" // S "@original" & buildRoute+  route (DeleteOriginalInteractionResponse (getID @Application -> aid) token) =+    baseRoute aid token // S "messages" // S "@original" & buildRoute+  route (CreateFollowupMessage (getID @Application -> aid) token _) =+    baseRoute aid token & buildRoute+  route (GetFollowupMessage (getID @Application -> aid) (getID @Message -> mid) token) =+    baseRoute aid token // S "messages" // ID @Message & giveID mid & buildRoute+  route (EditFollowupMessage (getID @Application -> aid) (getID @Message -> mid) token _) =+    baseRoute aid token // S "messages" // ID @Message & giveID mid & buildRoute+  route (DeleteFollowupMessage (getID @Application -> aid) (getID @Message -> mid) token) =+    baseRoute aid token // S "messages" // ID @Message & giveID mid & buildRoute++  action (CreateResponseDefer _ _ ephemeral) =+    let jsonBody =+          InteractionCallback+            { type_ = DeferredChannelMessageWithSourceType+            , data_ = if ephemeral then Just . object $ [("flags", Number 64)] else Nothing+            }+     in postWith' (ReqBodyJson jsonBody)+  action (CreateResponseDeferComponent _ _) =+    let jsonBody =+          InteractionCallback+            { type_ = DeferredUpdateMessageType+            , data_ = Nothing+            }+     in postWith' (ReqBodyJson jsonBody)+  action (CreateResponseMessage _ _ cm) = \u o -> do+    let filePart CreateMessageAttachment {filename, content} n =+          (partLBS @IO [fmt|files[{n}]|] content)+            { partFilename = Just (T.unpack filename)+            , partContentType = Just (defaultMimeLookup filename)+            }+        attachmentPart CreateMessageAttachment {filename, description} n =+          CreateMessageAttachmentJson n filename description+        files = zipWith filePart (fromMaybe [] $ cm ^. #attachments) [(0 :: Int) ..]+        attachments = (\a -> zipWith attachmentPart a [0 ..]) <$> cm ^. #attachments+        ephemeral = (\f -> if f then 1 `shiftL` 6 else 0) <$> cm ^. #ephemeral+        suppressEmbeds = (\f -> if f then 1 `shiftL` 2 else 0) <$> cm ^. #suppressEmbeds+        flags = foo ephemeral suppressEmbeds (.|.)+        jsonData =+          CreateResponseMessageJson+            { content = cm ^. #content+            , tts = cm ^. #tts+            , embeds = cm ^. #embeds+            , allowedMentions = cm ^. #allowedMentions+            , components = cm ^. #components+            , attachments = attachments+            , flags = flags+            }+        jsonBody =+          InteractionCallback+            { type_ = ChannelMessageWithSourceType+            , data_ = Just . toJSON $ jsonData+            }+    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonBody) : files)+    postWith' body u o+  action (CreateResponseUpdate _ _ cm) = \u o -> do+    let filePart CreateMessageAttachment {filename, content} n =+          (partLBS @IO [fmt|files[{n}]|] content)+            { partFilename = Just (T.unpack filename)+            , partContentType = Just (defaultMimeLookup filename)+            }+        attachmentPart CreateMessageAttachment {filename, description} n =+          CreateMessageAttachmentJson n filename description+        files = zipWith filePart (fromMaybe [] $ cm ^. #attachments) [(0 :: Int) ..]+        attachments = (\a -> zipWith attachmentPart a [0 ..]) <$> cm ^. #attachments+        ephemeral = (\f -> if f then 1 `shiftL` 6 else 0) <$> cm ^. #ephemeral+        suppressEmbeds = (\f -> if f then 1 `shiftL` 2 else 0) <$> cm ^. #suppressEmbeds+        flags = foo ephemeral suppressEmbeds (.|.)+        jsonData =+          CreateResponseMessageJson+            { content = cm ^. #content+            , tts = cm ^. #tts+            , embeds = cm ^. #embeds+            , allowedMentions = cm ^. #allowedMentions+            , components = cm ^. #components+            , attachments = attachments+            , flags = flags+            }+        jsonBody =+          InteractionCallback+            { type_ = UpdateMessageType+            , data_ = Just . toJSON $ jsonData+            }+    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonBody) : files)+    postWith' body u o+  action (CreateResponseAutocomplete _ _ ao) =+    let jsonBody =+          InteractionCallback+            { type_ = ApplicationCommandAutocompleteResultType+            , data_ = Just . toJSON $ ao+            }+     in postWith' (ReqBodyJson jsonBody)+  action (CreateResponseModal _ _ mo) =+    let jsonBody =+          InteractionCallback+            { type_ = ModalType+            , data_ = Just . toJSON $ mo+            }+     in postWith' (ReqBodyJson jsonBody)+  action (GetOriginalInteractionResponse _ _) = getWith+  action (EditOriginalInteractionResponse _ _ cm) = \u o -> do+    let filePart CreateMessageAttachment {filename, content} n =+          (partLBS @IO [fmt|files[{n}]|] content)+            { partFilename = Just (T.unpack filename)+            , partContentType = Just (defaultMimeLookup filename)+            }+        attachmentPart CreateMessageAttachment {filename, description} n =+          CreateMessageAttachmentJson n filename description+        files = zipWith filePart (fromMaybe [] $ cm ^. #attachments) [(0 :: Int) ..]+        attachments = (\a -> zipWith attachmentPart a [0 ..]) <$> cm ^. #attachments+        ephemeral = (\f -> if f then 1 `shiftL` 6 else 0) <$> cm ^. #ephemeral+        suppressEmbeds = (\f -> if f then 1 `shiftL` 2 else 0) <$> cm ^. #suppressEmbeds+        flags = foo ephemeral suppressEmbeds (.|.)+        jsonData =+          CreateResponseMessageJson+            { content = cm ^. #content+            , tts = cm ^. #tts+            , embeds = cm ^. #embeds+            , allowedMentions = cm ^. #allowedMentions+            , components = cm ^. #components+            , attachments = attachments+            , flags = flags+            }+        jsonBody =+          InteractionCallback+            { type_ = UpdateMessageType+            , data_ = Just . toJSON $ jsonData+            }+    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonBody) : files)+    patchWith' body u o+  action (DeleteOriginalInteractionResponse _ _) = deleteWith+  action (CreateFollowupMessage _ _ cm) = \u o -> do+    let filePart CreateMessageAttachment {filename, content} n =+          (partLBS @IO [fmt|files[{n}]|] content)+            { partFilename = Just (T.unpack filename)+            , partContentType = Just (defaultMimeLookup filename)+            }+        attachmentPart CreateMessageAttachment {filename, description} n =+          CreateMessageAttachmentJson n filename description+        files = zipWith filePart (fromMaybe [] $ cm ^. #attachments) [(0 :: Int) ..]+        attachments = (\a -> zipWith attachmentPart a [0 ..]) <$> cm ^. #attachments+        ephemeral = (\f -> if f then 1 `shiftL` 6 else 0) <$> cm ^. #ephemeral+        suppressEmbeds = (\f -> if f then 1 `shiftL` 2 else 0) <$> cm ^. #suppressEmbeds+        flags = foo ephemeral suppressEmbeds (.|.)+        jsonData =+          CreateResponseMessageJson+            { content = cm ^. #content+            , tts = cm ^. #tts+            , embeds = cm ^. #embeds+            , allowedMentions = cm ^. #allowedMentions+            , components = cm ^. #components+            , attachments = attachments+            , flags = flags+            }+    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonData) : files)+    postWith' body u o+  action GetFollowupMessage {} = getWith+  action (EditFollowupMessage _ _ _ cm) = \u o -> do+    let filePart CreateMessageAttachment {filename, content} n =+          (partLBS @IO [fmt|files[{n}]|] content)+            { partFilename = Just (T.unpack filename)+            , partContentType = Just (defaultMimeLookup filename)+            }+        attachmentPart CreateMessageAttachment {filename, description} n =+          CreateMessageAttachmentJson n filename description+        files = zipWith filePart (fromMaybe [] $ cm ^. #attachments) [(0 :: Int) ..]+        attachments = (\a -> zipWith attachmentPart a [0 ..]) <$> cm ^. #attachments+        ephemeral = (\f -> if f then 1 `shiftL` 6 else 0) <$> cm ^. #ephemeral+        suppressEmbeds = (\f -> if f then 1 `shiftL` 2 else 0) <$> cm ^. #suppressEmbeds+        flags = foo ephemeral suppressEmbeds (.|.)+        jsonData =+          CreateResponseMessageJson+            { content = cm ^. #content+            , tts = cm ^. #tts+            , embeds = cm ^. #embeds+            , allowedMentions = cm ^. #allowedMentions+            , components = cm ^. #components+            , attachments = attachments+            , flags = flags+            }+    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonData) : files)+    patchWith' body u o+  action DeleteFollowupMessage {} = deleteWith
Calamity/HTTP/Internal/Ratelimit.hs view
@@ -1,13 +1,18 @@+{-# LANGUAGE TemplateHaskell #-}+ -- | Module containing ratelimit stuff module Calamity.HTTP.Internal.Ratelimit (-    newRateLimitState,-    doRequest,+  newRateLimitState,+  doRequest,+  RatelimitEff (..),+  getRatelimitState, ) where -import Calamity.Client.Types (BotC) import Calamity.HTTP.Internal.Route import Calamity.HTTP.Internal.Types import Calamity.Internal.Utils+import Calamity.Types.LogEff+import Calamity.Types.TokenEff import Control.Applicative import Control.Concurrent import Control.Concurrent.Event (Event)@@ -27,13 +32,18 @@ import Network.HTTP.Client (responseStatus) import Network.HTTP.Req import Network.HTTP.Types-import Polysemy (Sem)+import Polysemy (Sem, makeSem) import qualified Polysemy as P import PyF import qualified StmContainers.Map as SC import Prelude hiding (error) import qualified Prelude +data RatelimitEff m a where+  GetRatelimitState :: RatelimitEff m RateLimitState++makeSem ''RatelimitEff+ newRateLimitState :: IO RateLimitState newRateLimitState = RateLimitState <$> SC.newIO <*> SC.newIO <*> E.newSet @@ -73,23 +83,23 @@       SC.insert bs b $ buckets s       SC.insert b h $ bucketKeys s       pure bs- where-  mergeStates :: BucketState -> BucketState -> BucketState-  mergeStates old new =-    new-      { ongoing = old ^. #ongoing-      , -- we only ignore the previous 'remaining' if we've not reset yet and the-        -- reset time has changed-        remaining =-          if isJust (old ^. #resetTime) && (old ^. #resetKey /= new ^. #resetKey)-            then min (old ^. #remaining) (new ^. #remaining)-            else new ^. #remaining-      , -- only take the new resetTime if it actually changed-        resetTime =-          if old ^. #resetKey /= new ^. #resetKey-            then new ^. #resetTime-            else old ^. #resetTime-      }+  where+    mergeStates :: BucketState -> BucketState -> BucketState+    mergeStates old new =+      new+        { ongoing = old ^. #ongoing+        , -- we only ignore the previous 'remaining' if we've not reset yet and the+          -- reset time has changed+          remaining =+            if isJust (old ^. #resetTime) && old ^. #resetKey /= new ^. #resetKey+              then min (old ^. #remaining) (new ^. #remaining)+              else new ^. #remaining+        , -- only take the new resetTime if it actually changed+          resetTime =+            if old ^. #resetKey /= new ^. #resetKey+              then new ^. #resetTime+              else old ^. #resetTime+        }  resetBucket :: Bucket -> STM () resetBucket bucket =@@ -101,7 +111,7 @@     )  canResetBucketNow :: UTCTime -> BucketState -> Bool-canResetBucketNow _ BucketState{ongoing} | ongoing > 0 = False+canResetBucketNow _ BucketState {ongoing} | ongoing > 0 = False -- don't allow resetting the bucket if there's ongoing requests, we'll wait -- until another request finishes and updates the counter canResetBucketNow now bs = case bs ^. #resetTime of@@ -112,7 +122,7 @@ -- canResetBucket bs = isNothing $ bs ^. #startedWaitingTime  shouldWaitForUnlock :: BucketState -> Bool-shouldWaitForUnlock BucketState{remaining = 0, ongoing} = ongoing > 0+shouldWaitForUnlock BucketState {remaining = 0, ongoing} = ongoing > 0 shouldWaitForUnlock _ = False  data WaitDelay@@ -128,69 +138,69 @@ -- | Maybe wait for a bucket, updating its state to say we used it useBucketOnce :: Bucket -> IO () useBucketOnce bucket = go 0- where-  go :: Int -> IO ()-  go tries = do-    now <- getCurrentTime-    mWaitDelay <- atomically $ do-      s <- readTVar $ bucket ^. #state+  where+    go :: Int -> IO ()+    go tries = do+      now <- getCurrentTime+      mWaitDelay <- atomically $ do+        s <- readTVar $ bucket ^. #state -      -- -- [0]-      -- -- if there are ongoing requests, wait for them to finish and deliver-      -- -- truth on the current ratelimit state-      when-        (shouldWaitForUnlock s)-        retry+        -- -- [0]+        -- -- if there are ongoing requests, wait for them to finish and deliver+        -- -- truth on the current ratelimit state+        when+          (shouldWaitForUnlock s)+          retry -      -- if there are no ongoing requests, and the bucket reset time has lapsed,-      -- we can just reset the bucket.-      ---      -- if we've already reset the bucket then there should be an ongoing-      -- request so we'll just end up waiting for that to finish-      when-        (canResetBucketNow now s)-        (resetBucket bucket)+        -- if there are no ongoing requests, and the bucket reset time has lapsed,+        -- we can just reset the bucket.+        --+        -- if we've already reset the bucket then there should be an ongoing+        -- request so we'll just end up waiting for that to finish+        when+          (canResetBucketNow now s)+          (resetBucket bucket) -      s <- readTVar $ bucket ^. #state+        s <- readTVar $ bucket ^. #state -      if s ^. #remaining - s ^. #ongoing > 0-        then do-          -- there are tokens remaining for us to use-          modifyTVar'-            (bucket ^. #state)-            ( (#remaining -~ 1)-                . (#ongoing +~ 1)-            )-          pure GoNow-        else do-          -- the bucket has expired, there are no ongoing requests because of-          -- [0] wait and then retry after we can unlock the bucket-          pure (intoWaitDelay $ s ^. #resetTime)+        if s ^. #remaining - s ^. #ongoing > 0+          then do+            -- there are tokens remaining for us to use+            modifyTVar'+              (bucket ^. #state)+              ( (#remaining -~ 1)+                  . (#ongoing +~ 1)+              )+            pure GoNow+          else do+            -- the bucket has expired, there are no ongoing requests because of+            -- [0] wait and then retry after we can unlock the bucket+            pure (intoWaitDelay $ s ^. #resetTime) -    -- putStrLn (show now <> ": Using bucket, waiting until: " <> show mWaitDelay <> ", uses: " <> show s <> ", " <> inf)+      -- putStrLn (show now <> ": Using bucket, waiting until: " <> show mWaitDelay <> ", uses: " <> show s <> ", " <> inf) -    case mWaitDelay of-      WaitUntil waitUntil -> do-        if waitUntil < now-          then threadDelayMS 20-          else -- if the reset is in the past, we're fucked-            threadDelayUntil waitUntil-        -- if we needed to sleep, go again so that multiple concurrent requests-        -- don't exceed the bucket, to ensure we don't sit in a loop if a-        -- request dies on us, bail out after 50 loops-        if tries < 50-          then go (tries + 1)-          else pure () -- print "bailing after number of retries"-      WaitRetrySoon -> do-        threadDelayMS 20-        if tries < 50-          then go (tries + 1)-          else pure () -- print "bailing after number of retries"-      GoNow -> do-        -- print "ok going forward with request"-        pure ()+      case mWaitDelay of+        WaitUntil waitUntil -> do+          if waitUntil < now+            then threadDelayMS 20+            else -- if the reset is in the past, we're fucked+              threadDelayUntil waitUntil+          -- if we needed to sleep, go again so that multiple concurrent requests+          -- don't exceed the bucket, to ensure we don't sit in a loop if a+          -- request dies on us, bail out after 50 loops+          if tries < 50+            then go (tries + 1)+            else pure () -- print "bailing after number of retries"+        WaitRetrySoon -> do+          threadDelayMS 20+          if tries < 50+            then go (tries + 1)+            else pure () -- print "bailing after number of retries"+        GoNow -> do+          -- print "ok going forward with request"+          pure () -doDiscordRequest :: BotC r => IO LbsResponse -> Sem r DiscordResponseType+doDiscordRequest :: P.Members '[RatelimitEff, TokenEff, LogEff, P.Embed IO] r => IO LbsResponse -> Sem r DiscordResponseType doDiscordRequest r = do   r'' <- P.embed $ Ex.catchAny (Right <$> r) (pure . Left . Ex.displayException)   case r'' of@@ -225,33 +235,33 @@ -- | Parse a ratelimit header returning when it unlocks parseRateLimitHeader :: HttpResponse r => UTCTime -> r -> Maybe UTCTime parseRateLimitHeader now r = computedEnd <|> end- where-  computedEnd :: Maybe UTCTime-  computedEnd = flip addUTCTime now <$> resetAfter+  where+    computedEnd :: Maybe UTCTime+    computedEnd = flip addUTCTime now <$> resetAfter -  resetAfter :: Maybe NominalDiffTime-  resetAfter = realToFrac <$> responseHeader r "X-Ratelimit-Reset-After" ^? _Just . _Double+    resetAfter :: Maybe NominalDiffTime+    resetAfter = realToFrac <$> responseHeader r "X-Ratelimit-Reset-After" ^? _Just . _Double -  end :: Maybe UTCTime-  end =-    posixSecondsToUTCTime . realToFrac-      <$> responseHeader r "X-Ratelimit-Reset" ^? _Just . _Double+    end :: Maybe UTCTime+    end =+      posixSecondsToUTCTime . realToFrac+        <$> responseHeader r "X-Ratelimit-Reset" ^? _Just . _Double  buildBucketState :: HttpResponse r => UTCTime -> r -> Maybe (BucketState, B.ByteString) buildBucketState now r = (,) <$> bs <*> bucketKey- where-  remaining = responseHeader r "X-RateLimit-Remaining" ^? _Just . _Integral-  limit = responseHeader r "X-RateLimit-Limit" ^? _Just . _Integral-  resetKey = ceiling <$> responseHeader r "X-RateLimit-Reset" ^? _Just . _Double-  resetTime = parseRateLimitHeader now r-  bs = BucketState resetTime <$> resetKey <*> remaining <*> limit <*> pure 0-  bucketKey = responseHeader r "X-RateLimit-Bucket"+  where+    remaining = responseHeader r "X-RateLimit-Remaining" ^? _Just . _Integral+    limit = responseHeader r "X-RateLimit-Limit" ^? _Just . _Integral+    resetKey = ceiling <$> responseHeader r "X-RateLimit-Reset" ^? _Just . _Double+    resetTime = parseRateLimitHeader now r+    bs = BucketState resetTime <$> resetKey <*> remaining <*> limit <*> pure 0+    bucketKey = responseHeader r "X-RateLimit-Bucket"  -- | Parse the retry after field, returning when to retry parseRetryAfter :: UTCTime -> Value -> UTCTime parseRetryAfter now r = addUTCTime retryAfter now- where-  retryAfter = realToFrac $ r ^?! key "retry_after" . _Double+  where+    retryAfter = realToFrac $ r ^?! key "retry_after" . _Double  isGlobal :: Value -> Bool isGlobal r = r ^? key "global" . _Bool == Just True@@ -263,27 +273,27 @@   | RGood b  retryRequest ::-  BotC r =>+  P.Members '[RatelimitEff, TokenEff, LogEff, P.Embed IO] r =>   -- | number of retries   Int ->   -- | action to perform   Sem r (ShouldRetry a b) ->   Sem r (Either a b) retryRequest maxRetries action = retryInner 0- where-  retryInner numRetries = do-    res <- action-    case res of-      Retry r | numRetries > maxRetries -> do-        debug [fmt|Request failed after {maxRetries} retries|]-        pure $ Left r-      Retry _ ->-        retryInner (numRetries + 1)-      RFail r -> do-        debug "Request failed due to error response"-        pure $ Left r-      RGood r ->-        pure $ Right r+  where+    retryInner numRetries = do+      res <- action+      case res of+        Retry r | numRetries > maxRetries -> do+          debug [fmt|Request failed after {maxRetries} retries|]+          pure $ Left r+        Retry _ ->+          retryInner (numRetries + 1)+        RFail r -> do+          debug "Request failed due to error response"+          pure $ Left r+        RGood r ->+          pure $ Right r  threadDelayMS :: Int -> IO () threadDelayMS ms = threadDelay (1000 * ms)@@ -300,7 +310,7 @@  -- Run a single request doSingleRequest ::-  BotC r =>+  P.Members '[RatelimitEff, TokenEff, LogEff, P.Embed IO] r =>   RateLimitState ->   Route ->   -- | Global lock@@ -395,7 +405,7 @@         _ -> debug "unknown ratelimit"       pure $ RFail (HTTPError c $ decode v) -doRequest :: BotC r => RateLimitState -> Route -> IO LbsResponse -> Sem r (Either RestError LB.ByteString)+doRequest :: P.Members '[RatelimitEff, TokenEff, LogEff, P.Embed IO] r => RateLimitState -> Route -> IO LbsResponse -> Sem r (Either RestError LB.ByteString) doRequest rlstate route action =   retryRequest     5
Calamity/HTTP/Internal/Request.hs view
@@ -16,37 +16,31 @@   (=:?), ) where -import Calamity.Client.Types import Calamity.HTTP.Internal.Ratelimit import Calamity.HTTP.Internal.Route import Calamity.HTTP.Internal.Types import Calamity.Metrics.Eff+import Calamity.Types.LogEff (LogEff) import Calamity.Types.Token-+import Calamity.Types.TokenEff import Control.Lens import Control.Monad- import Data.Aeson hiding (Options) import Data.Aeson.Types (parseEither) import qualified Data.ByteString.Lazy as LB import qualified Data.Text as T import qualified Data.Text.Encoding as TS- import DiPolysemy hiding (debug, error, info)- import Network.HTTP.Req-import Web.HttpApiData--import Polysemy (Sem) import qualified Polysemy as P import qualified Polysemy.Error as P-import qualified Polysemy.Reader as P+import Web.HttpApiData -throwIfLeft :: P.Member (P.Error RestError) r => Either String a -> Sem r a+throwIfLeft :: P.Member (P.Error RestError) r => Either String a -> P.Sem r a throwIfLeft (Right a) = pure a throwIfLeft (Left e) = P.throw (InternalClientError . T.pack $ e) -extractRight :: P.Member (P.Error e) r => Either e a -> Sem r a+extractRight :: P.Member (P.Error e) r => Either e a -> P.Sem r a extractRight (Left e) = P.throw e extractRight (Right a) = pure a @@ -69,10 +63,16 @@   modifyResponse :: a -> Value -> Value   modifyResponse _ = id -invoke :: (BotC r, Request a, ReadResponse (Calamity.HTTP.Internal.Request.Result a)) => a -> Sem r (Either RestError (Calamity.HTTP.Internal.Request.Result a))+invoke ::+  ( P.Members '[RatelimitEff, TokenEff, LogEff, MetricEff, P.Embed IO] r+  , Request a+  , ReadResponse (Calamity.HTTP.Internal.Request.Result a)+  ) =>+  a ->+  P.Sem r (Either RestError (Calamity.HTTP.Internal.Request.Result a)) invoke a = do-  rlState' <- P.asks (^. #rlState)-  token' <- P.asks (^. #token)+  rlState' <- getRatelimitState+  token' <- getBotToken    let route' = route a @@ -141,4 +141,4 @@  (=:?) :: ToHttpApiData a => T.Text -> Maybe a -> Option 'Https n =:? (Just x) = n =: x-n =:? Nothing = mempty+_ =:? Nothing = mempty
Calamity/HTTP/Internal/Route.hs view
@@ -165,7 +165,7 @@ routeKey Route {key, channelID, guildID} = (key, channelID, guildID)  baseURL :: Url 'Https-baseURL = https "discord.com" /: "api" /: "v9"+baseURL = https "discord.com" /: "api" /: "v10"  buildRoute   :: forall (reqs :: [(RequirementType, RouteRequirement)])
Calamity/HTTP/Webhook.hs view
@@ -6,26 +6,23 @@   ExecuteWebhookOptions (..), ) where +import Calamity.HTTP.Channel (AllowedMentions, CreateMessageAttachment (..)) import Calamity.HTTP.Internal.Request import Calamity.HTTP.Internal.Route import Calamity.Internal.AesonThings import Calamity.Types.Model.Channel import Calamity.Types.Model.Guild import Calamity.Types.Snowflake- import Control.Lens hiding ((.=))- import Data.Aeson-import Data.ByteString.Lazy (ByteString) import Data.Default.Class-import Data.Generics.Product.Subtype (upcast) import Data.Text (Text)-+import qualified Data.Text as T import GHC.Generics--import Network.HTTP.Req- import Network.HTTP.Client.MultipartFormData+import Network.HTTP.Req+import Network.Mime+import PyF  data CreateWebhookData = CreateWebhookData   { username :: Maybe Text@@ -47,20 +44,33 @@ data ExecuteWebhookOptions = ExecuteWebhookOptions   { wait :: Maybe Bool   , content :: Maybe Text-  , file :: Maybe ByteString+  , attachments :: [CreateMessageAttachment]   , embeds :: Maybe [Embed]   , username :: Maybe Text   , avatarUrl :: Maybe Text+  , allowedMentions :: Maybe AllowedMentions   , tts :: Maybe Bool+  , components :: [Component]   }   deriving (Show, Generic, Default) +data CreateMessageAttachmentJson = CreateMessageAttachmentJson+  { id :: Int+  , filename :: Text+  , description :: Maybe Text+  }+  deriving (Show, Generic)+  deriving (ToJSON) via CalamityJSON CreateMessageAttachmentJson+ data ExecuteWebhookJson = ExecuteWebhookJson   { content :: Maybe Text   , embeds :: Maybe [Embed]   , username :: Maybe Text   , avatarUrl :: Maybe Text   , tts :: Maybe Bool+  , attachments :: [CreateMessageAttachmentJson]+  , allowedMentions :: Maybe AllowedMentions+  , components :: [Component]   }   deriving (Show, Generic)   deriving (ToJSON) via CalamityJSON ExecuteWebhookJson@@ -126,10 +136,26 @@   action (ModifyWebhookToken _ _ o) = patchWith' $ ReqBodyJson o   action (DeleteWebhook _) = deleteWith   action (DeleteWebhookToken _ _) = deleteWith-  action (ExecuteWebhook _ _ o@ExecuteWebhookOptions{file = Nothing}) =-    postWithP'-      (ReqBodyJson . upcast @ExecuteWebhookJson $ o)-      ("wait" =:? (o ^. #wait))-  action (ExecuteWebhook _ _ wh@ExecuteWebhookOptions{file = Just f}) = \u o -> do-    body <- reqBodyMultipart [partLBS @IO "file" f, partLBS "payload_json" (encode . upcast @ExecuteWebhookJson $ wh)]+  action (ExecuteWebhook _ _ wh) = \u o -> do+    let filePart CreateMessageAttachment {filename, content} n =+          (partLBS @IO [fmt|files[{n}]|] content)+            { partFilename = Just (T.unpack filename)+            , partContentType = Just (defaultMimeLookup filename)+            }+        attachmentPart CreateMessageAttachment {filename, description} n =+          CreateMessageAttachmentJson n filename description+        files = zipWith filePart (wh ^. #attachments) [(0 :: Int) ..]+        attachments = zipWith attachmentPart (wh ^. #attachments) [0 ..]+        jsonBody =+          ExecuteWebhookJson+            { content = wh ^. #content+            , username = wh ^. #username+            , avatarUrl = wh ^. #avatarUrl+            , tts = wh ^. #tts+            , embeds = wh ^. #embeds+            , allowedMentions = wh ^. #allowedMentions+            , components = wh ^. #components+            , attachments = attachments+            }+    body <- reqBodyMultipart (partLBS "payload_json" (encode jsonBody) : files)     postWithP' body ("wait" =:? (wh ^. #wait)) u o
+ Calamity/Interactions.hs view
@@ -0,0 +1,64 @@+-- | Calamity Interaction views+module Calamity.Interactions (+  module Calamity.Interactions.View,+  module Calamity.Interactions.Utils,+  module Calamity.Interactions.Eff,++  -- * Interactions and Views+  -- $viewDocs+) where++import Calamity.Interactions.Eff+import Calamity.Interactions.Utils+import Calamity.Interactions.View++{- $viewDocs++ This module contains functions and types related to handling discord interactions.++ The data models for components can be found in "Calamity.Types.Model.Channel.Component"++ ==== Examples++ Displaying a 'View'++ @+ {-# LANGUAGE ApplicativeDo #-}++ let view = 'row' $ do+       a <- 'button' 'Calamity.Types.Model.Channel.Component.ButtonPrimary' "defer"+       b <- 'button' 'Calamity.Types.Model.Channel.Component.ButtonPrimary' "deferEph"+       c <- 'button' 'Calamity.Types.Model.Channel.Component.ButtonPrimary' "deferComp"+       d <- 'button' 'Calamity.Types.Model.Channel.Component.ButtonPrimary' "modal"+       pure (a, b, c, d)++     modalView = do+       a <- 'textInput' 'Calamity.Types.Model.Channel.Component.TextInputShort' "a"+       b <- 'textInput' 'Calamity.Types.Model.Channel.Component.TextInputParagraph' "b"+       pure (a, b)+ in 'runView' view ('Calamity.tell' ctx) $ \\(a, b, c, d) -> do+      'Control.Monad.when' a $ do+        'Control.Monad.void' 'defer'+        'Polysemy.embed' $ threadDelay 1000000+        'Control.Monad.void' $ 'followUp' \@'Data.Text.Text' "lol"++      'Control.Monad.when' b $ do+        'Control.Monad.void' 'deferEphemeral'+        'Polysemy.embed' $ threadDelay 1000000+        'Control.Monad.void' $ 'followUpEphemeral' \@'Data.Text.Text' "lol"++      'Control.Monad.when' c $ do+        'Control.Monad.void' 'deferComponent'+        'Polysemy.embed' $ threadDelay 1000000+        'Control.Monad.void' $ 'followUp' \@'Data.Text.Text' "lol"++      'Control.Monad.when' d $ do+        'Control.Monad.void' . 'Polysemy.Async.async' $ do+          'runView' modalView ('Control.Monad.void' . 'pushModal' "lol") $ \(a, b) -> do+            'Polysemy.embed' $ print (a, b)+            'Control.Monad.void' $ 'respond' ("Thanks: " <> a <> " " <> b)+            'endView' ()++      pure ()+ @+-}
+ Calamity/Interactions/Eff.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE TemplateHaskell #-}++-- | Effect for working with an interaction+module Calamity.Interactions.Eff (+  InteractionEff (..),+  getInteraction,+  getInteractionID,+  getApplicationID,+  getInteractionToken,+  getInteractionUser,+) where++import Calamity.Types.Model.Interaction+import Calamity.Types.Model.User (User)+import Calamity.Types.Snowflake+import Control.Applicative ((<|>))+import Control.Lens ((^.), (^?), _Just)+import Data.Maybe (fromJust)+import Polysemy+import qualified Polysemy as P++data InteractionEff m a where+  GetInteraction :: InteractionEff m Interaction++makeSem ''InteractionEff++getInteractionID :: P.Member InteractionEff r => P.Sem r (Snowflake Interaction)+getInteractionID = (^. #id) <$> getInteraction++getApplicationID :: P.Member InteractionEff r => P.Sem r (Snowflake Application)+getApplicationID = (^. #applicationID) <$> getInteraction++getInteractionToken :: P.Member InteractionEff r => P.Sem r InteractionToken+getInteractionToken = (^. #token) <$> getInteraction++getInteractionUser :: P.Member InteractionEff r => P.Sem r (Snowflake User)+getInteractionUser = do+  int <- getInteraction+  let uid = int ^? #user . _Just . #id+      mid = int ^? #member . _Just . #id+  pure . fromJust $ uid <|> mid+
+ Calamity/Interactions/Utils.hs view
@@ -0,0 +1,199 @@+-- | Interaction related utilities+module Calamity.Interactions.Utils (+  respond,+  respondEphemeral,+  followUp,+  followUpEphemeral,+  edit,+  defer,+  deferEphemeral,+  deferComponent,+  pushModal,+  userLocalState,+) where++import Calamity.HTTP+import Calamity.Interactions.Eff (InteractionEff, getInteractionID, getInteractionToken, getInteractionUser, getApplicationID)+import Calamity.Metrics.Eff (MetricEff)+import Calamity.Types.LogEff (LogEff)+import Calamity.Types.Model.Channel.Component (Component (ActionRow'))+import Calamity.Types.Model.User (User)+import Calamity.Types.Snowflake (Snowflake)+import Calamity.Types.Tellable+import Control.Lens ((^.))+import qualified Data.HashMap.Strict as H+import Data.Text (Text)+import qualified Polysemy as P+import qualified Polysemy.State as P+import System.Random (getStdRandom, uniform)++{- | Provide local state semantics to a running view, the state is keyed to the+ user invoking the interaction+-}+userLocalState ::+  forall r s a.+  P.Member InteractionEff r =>+  -- | Initial state+  s ->+  P.Sem (P.State s ': r) a ->+  P.Sem r a+userLocalState s =+  P.evalState H.empty+    . P.reinterpret @(P.State s) @(P.State (H.HashMap (Snowflake User) s))+      ( \case+          P.Get -> do+            uid <- getInteractionUser+            P.gets (H.lookupDefault s uid)+          P.Put s -> do+            uid <- getInteractionUser+            P.modify' (H.insert uid s)+      )++-- | Respond to an interaction with a globally visible message+respond :: forall t r.+  (P.Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, P.Embed IO] r, ToMessage t) =>+  t ->+  P.Sem r (Either RestError ())+respond (runToMessage -> msg) =+  let opts =+        InteractionCallbackMessageOptions+          { tts = msg ^. #tts+          , content = msg ^. #content+          , embeds = msg ^. #embeds+          , allowedMentions = msg ^. #allowedMentions+          , ephemeral = Just False+          , suppressEmbeds = Just False+          , components = msg ^. #components+          , attachments = msg ^. #attachments+          }+   in do+        interactionID <- getInteractionID+        interactionToken <- getInteractionToken+        invoke $ CreateResponseMessage interactionID interactionToken opts++-- | Respond to an interaction with an ephemeral message+respondEphemeral :: forall t r.+  (P.Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, P.Embed IO] r, ToMessage t) =>+  t ->+  P.Sem r (Either RestError ())+respondEphemeral (runToMessage -> msg) =+  let opts =+        InteractionCallbackMessageOptions+          { tts = msg ^. #tts+          , content = msg ^. #content+          , embeds = msg ^. #embeds+          , allowedMentions = msg ^. #allowedMentions+          , ephemeral = Just True+          , suppressEmbeds = Just False+          , components = msg ^. #components+          , attachments = msg ^. #attachments+          }+   in do+        interactionID <- getInteractionID+        interactionToken <- getInteractionToken+        invoke $ CreateResponseMessage interactionID interactionToken opts++-- | Respond to an interaction by editing the message that triggered the interaction+edit :: forall t r.+  (P.Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, P.Embed IO] r, ToMessage t) =>+  t ->+  P.Sem r (Either RestError ())+edit (runToMessage -> msg) =+  let opts =+        InteractionCallbackMessageOptions+          { tts = msg ^. #tts+          , content = msg ^. #content+          , embeds = msg ^. #embeds+          , allowedMentions = msg ^. #allowedMentions+          , ephemeral = Nothing+          , suppressEmbeds = Nothing+          , components = msg ^. #components+          , attachments = msg ^. #attachments+          }+   in do+        interactionID <- getInteractionID+        interactionToken <- getInteractionToken+        invoke $ CreateResponseUpdate interactionID interactionToken opts++-- | Create a follow up response to an interaction+followUp :: forall t r.+  (P.Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, P.Embed IO] r, ToMessage t) =>+  t ->+  P.Sem r (Either RestError ())+followUp (runToMessage -> msg) =+  let opts =+        InteractionCallbackMessageOptions+          { tts = msg ^. #tts+          , content = msg ^. #content+          , embeds = msg ^. #embeds+          , allowedMentions = msg ^. #allowedMentions+          , ephemeral = Nothing+          , suppressEmbeds = Nothing+          , components = msg ^. #components+          , attachments = msg ^. #attachments+          }+   in do+        applicationID <- getApplicationID+        interactionToken <- getInteractionToken+        invoke $ CreateFollowupMessage applicationID interactionToken opts++-- | Create an ephemeral follow up response to an interaction+followUpEphemeral :: forall t r.+  (P.Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, P.Embed IO] r, ToMessage t) =>+  t ->+  P.Sem r (Either RestError ())+followUpEphemeral (runToMessage -> msg) =+  let opts =+        InteractionCallbackMessageOptions+          { tts = msg ^. #tts+          , content = msg ^. #content+          , embeds = msg ^. #embeds+          , allowedMentions = msg ^. #allowedMentions+          , ephemeral = Just True+          , suppressEmbeds = Nothing+          , components = msg ^. #components+          , attachments = msg ^. #attachments+          }+   in do+        applicationID <- getApplicationID+        interactionToken <- getInteractionToken+        invoke $ CreateFollowupMessage applicationID interactionToken opts++-- | Defer an interaction and show a loading state, use @followUp@ later on+defer :: P.Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, P.Embed IO] r => P.Sem r (Either RestError ())+defer = do+  interactionID <- getInteractionID+  interactionToken <- getInteractionToken+  invoke $ CreateResponseDefer interactionID interactionToken False++-- | Defer an interaction and show an ephemeral loading state, use @followUp@+-- later on+deferEphemeral :: P.Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, P.Embed IO] r => P.Sem r (Either RestError ())+deferEphemeral = do+  interactionID <- getInteractionID+  interactionToken <- getInteractionToken+  invoke $ CreateResponseDefer interactionID interactionToken True++-- | Defer operation+deferComponent :: P.Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, P.Embed IO] r => P.Sem r (Either RestError ())+deferComponent = do+  interactionID <- getInteractionID+  interactionToken <- getInteractionToken+  invoke $ CreateResponseDeferComponent interactionID interactionToken++fixupActionRow :: Component -> Component+fixupActionRow r@(ActionRow' _) = r+fixupActionRow x = ActionRow' [x]++{- | Push a modal as a response to an interaction++ You should probably use this with 'Calamity.Interaction.View.runView'+-}+pushModal :: P.Members '[InteractionEff, RatelimitEff, TokenEff, LogEff, MetricEff, P.Embed IO] r => Text -> [Component] -> P.Sem r (Either RestError ())+pushModal title c = do+  -- we don't actually use the custom id of the modal. the custom ids of the+  -- sub-components are enough to disambiguate+  cid <- P.embed $ getStdRandom uniform+  interactionID <- getInteractionID+  interactionToken <- getInteractionToken+  invoke . CreateResponseModal interactionID interactionToken $ InteractionCallbackModal cid title (map fixupActionRow c)
+ Calamity/Interactions/View.hs view
@@ -0,0 +1,428 @@+{-# LANGUAGE TemplateHaskell #-}++module Calamity.Interactions.View (+  ViewEff (..),+  endView,+  replaceView,+  getSendResponse,+  View,+  row,+  runView,+  runViewInstance,+  button,+  button',+  select,+  select',+  textInput,+  textInput',+  deleteInitialMsg,+  instantiateView,+) where++import Calamity.Client.Client (react)+import Calamity.Client.Types (BotC, EventType (InteractionEvt))+import Calamity.HTTP.Channel (ChannelRequest (DeleteMessage))+import Calamity.HTTP.Internal.Ratelimit (RatelimitEff)+import Calamity.HTTP.Internal.Request (invoke)+import Calamity.Interactions.Eff (InteractionEff (..))+import Calamity.Internal.AesonThings+import Calamity.Metrics.Eff (MetricEff)+import Calamity.Types.LogEff (LogEff)+import Calamity.Types.Model.Channel.Component (CustomID)+import qualified Calamity.Types.Model.Channel.Component as C+import Calamity.Types.Model.Channel.Message (Message)+import Calamity.Types.Model.Interaction+import Calamity.Types.TokenEff (TokenEff)+import qualified Control.Concurrent.STM as STM+import Control.Lens ((.~), (?~), (^.), (^..), _1, _2, _3, _Just)+import Control.Monad (guard, void)+import qualified Data.Aeson+import Data.Aeson.Lens (AsValue (_Array), key)+import qualified Data.List+import qualified Data.Set as S+import Data.Text (Text)+import GHC.Generics (Generic)+import qualified GHC.TypeLits as E+import qualified Polysemy as P+import qualified Polysemy.Resource as P+import qualified Polysemy.State as P+import System.Random++data ViewComponent a = ViewComponent+  { component :: C.Component+  , parse :: Interaction -> ExtractResult a+  }++instance Functor ViewComponent where+  fmap f ViewComponent {component, parse} = ViewComponent {component, parse = fmap f . parse}++{- | A view containing one or more components++ This has an applicative interface to allow for easy composition of+ components.+-}+data View a+  = NilView a+  | SingView (forall g. RandomGen g => g -> (ViewComponent a, g))+  | RowView (View a)+  | forall x. MultView (View (x -> a)) (View x)++{- | Convert a 'View' such that it renders as a row.++ Note: nested rows are not allowed by discord, along with further restrictions+ listed here:+ https://discord.com/developers/docs/interactions/message-components+-}+row :: View a -> View a+row = RowView++instance Functor View where+  fmap f (NilView x) = NilView (f x)+  fmap f (SingView x) = SingView (\r -> let (x', r') = x r in (f <$> x', r'))+  fmap f (RowView x) = RowView (fmap f x)+  fmap f (MultView x y) = MultView (fmap (f .) x) y++instance Applicative View where+  pure = NilView+  (<*>) = MultView++type MonadViewMessage =+  'E.ShowType View+    'E.:<>: 'E.Text " doesn't have a Monad instance as we need to be able to inspect the contained components"+    'E.:$$: 'E.Text "If you haven't already, enable ApplicativeDo"+    'E.:$$: 'E.Text "Also, make sure you use lazy patterns: ~(a, b) instead of (a, b)"+    'E.:$$: 'E.Text "Refer to https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/applicative_do.html"++instance E.TypeError MonadViewMessage => Monad View where+  (>>=) = undefined -- unreachable++data ExtractOkType+  = -- | At least one value has been extracted+    SomeExtracted+  | -- | No values have been extracted, we shouldn't trigger the callback+    NoneExtracted+  deriving (Show)++instance Semigroup ExtractOkType where+  SomeExtracted <> _ = SomeExtracted+  _ <> SomeExtracted = SomeExtracted+  _ <> _ = NoneExtracted++data ExtractResult a+  = -- | Extraction succeeded in some way+    ExtractOk ExtractOkType a+  | -- | Bail out from parsing this interaction for the current view+    ExtractFail+  deriving (Show, Functor)++instance Applicative ExtractResult where+  pure = ExtractOk SomeExtracted++  ExtractOk ta f <*> ExtractOk tb x = ExtractOk (ta <> tb) $ f x+  _ <*> _ = ExtractFail++data ViewInstance a = ViewInstance+  { -- customIDS :: Set C.CustomID ,+    extract :: Interaction -> ExtractResult a+  , rendered :: [C.Component]+  }++data ViewEff ret inp sendResp m a where+  -- | Mark the view as finished and set the return value.+  --+  -- This doesn't trigger the immediate exit from the code it is used in, the+  -- view will exit before it would wait for the next event.+  EndView :: ret -> ViewEff ret inp sendResp m ()+  -- | Given a view and a way to display the rendered view to discord, show the+  -- view and start tracking the new view+  --+  -- This works for both message components and modals+  ReplaceView :: View inp -> ([C.Component] -> m ()) -> ViewEff ret inp sendResp m ()+  -- | Get the result of the action that sent a value+  GetSendResponse :: ViewEff ret inp sendResp m sendResp++P.makeSem ''ViewEff++extractCustomID :: Interaction -> Maybe CustomID+extractCustomID Interaction {data_, type_} = do+  guard $ type_ == MessageComponentType+  data' <- data_+  data' ^. #customID++guardComponentType :: Interaction -> C.ComponentType -> Maybe ()+guardComponentType Interaction {data_} expected = do+  data' <- data_+  guard $ data' ^. #componentType == Just expected++extractOkFromMaybe :: Maybe a -> ExtractResult (Maybe a)+extractOkFromMaybe (Just a) = ExtractOk SomeExtracted (Just a)+extractOkFromMaybe Nothing = ExtractOk NoneExtracted Nothing++extractOkFromBool :: Bool -> ExtractResult Bool+extractOkFromBool True = ExtractOk SomeExtracted True+extractOkFromBool False = ExtractOk NoneExtracted False++{- | Construct a 'View' containing a 'C.Button' with the given style and label++ Other fields of 'C.Button' default to 'Nothing'+-}+button :: C.ButtonStyle -> Text -> View Bool+button s l = button' ((#style .~ s) . (#label ?~ l))++{- | Construct a 'View' containing a 'C.Button', then modify the component with+   the passed mapping function++ The 'C.Button' passed to the mapping function will have a style of+ 'C.ButtonPrimary', other fields will be 'Nothing'+-}+button' :: (C.Button -> C.Button) -> View Bool+button' f = SingView $ \g ->+  let (cid, g') = uniform g+      comp = C.Button' . f $ C.button C.ButtonPrimary cid+      parse' int =+        Just True+          == ( do+                customID <- extractCustomID int+                guardComponentType int C.ButtonType+                pure $ customID == cid+             )+      parse = extractOkFromBool . parse'+   in (ViewComponent comp parse, g')++{- | Construct a 'View' containing a 'C.Select' with the given list of values++ Each element of the passed options list is used as both the display+ 'C.SelectOption.label' and 'C.SelectOption.value', use 'select'' if you+ desire more control+-}+select :: [Text] -> View (Maybe Text)+select opts = ensureOne <$> select' (map (\x -> C.sopt x x) opts) Prelude.id+  where+    ensureOne :: Maybe [Text] -> Maybe Text+    ensureOne mx = do+      o <- mx+      guard $ length o == 1+      case o of+        [x] -> Just x+        _ -> Nothing++{- | Construct a 'View' containing a 'C.Select' with the given options, then+ modify the component with the passed mapping function+-}+select' :: [C.SelectOption] -> (C.Select -> C.Select) -> View (Maybe [Text])+select' opts f = SingView $ \g ->+  let (cid, g') = uniform g+      comp = f $ C.select opts cid+      finalValues = S.fromList $ comp ^.. #options . traverse . #value+      parse int = extractOkFromMaybe $ do+        customID <- extractCustomID int+        guard $ customID == cid+        guardComponentType int C.SelectType+        values <- int ^. #data_ . _Just . #values+        let values' = S.fromList values+        guard $ S.isSubsetOf values' finalValues+        pure values+   in (ViewComponent (C.Select' comp) parse, g')++data TextInputDecoded = TextInputDecoded+  { value :: Maybe Text+  , customID :: CustomID+  }+  deriving (Show, Generic)+  deriving (Data.Aeson.FromJSON) via CalamityJSON TextInputDecoded++parseTextInput :: CustomID -> Interaction -> ExtractResult (Maybe Text)+parseTextInput cid int = extractOkFromMaybe $ do+  components <- int ^. #data_ . _Just . #components+  -- currently, each text input is a singleton actionrow containing a single textinput component++  let textInputs = components ^.. traverse . key "components" . _Array . traverse+      inputs' :: Data.Aeson.Result [TextInputDecoded] = traverse Data.Aeson.fromJSON textInputs++  inputs <- case inputs' of+    Data.Aeson.Success x -> pure x+    Data.Aeson.Error _ -> Nothing++  thisValue <- Data.List.find ((== cid) . (^. #customID)) inputs++  thisValue ^. #value++{- | Construct a 'View' containing a 'C.TextInput' with the given style and label++ All other fields of 'C.TextInput' default to 'Nothing'++ This view ensures that a value was passed for an input+-}+textInput ::+  C.TextInputStyle ->+  -- | Label+  Text ->+  View Text+textInput s l = SingView $ \g ->+  let (cid, g') = uniform g+      comp = C.TextInput' $ C.textInput s l cid+      parse = ensure <$> parseTextInput cid+   in (ViewComponent comp parse, g')+  where+    ensure (ExtractOk v (Just x)) = ExtractOk v x+    ensure _ = ExtractFail++{- | Construct a 'View' containing a 'C.TextInput' with the given style and label,+   then modify the component with the passed mapping function+-}+textInput' ::+  C.TextInputStyle ->+  -- | Label+  Text ->+  (C.TextInput -> C.TextInput) ->+  View (Maybe Text)+textInput' s l f = SingView $ \g ->+  let (cid, g') = uniform g+      comp = C.TextInput' . f $ C.textInput s l cid+      parse = parseTextInput cid+   in (ViewComponent comp parse, g')++-- componentIDS :: C.Component -> S.Set CustomID+-- componentIDS (C.ActionRow' coms) = S.unions $ map componentIDS coms+-- componentIDS (C.Button' C.Button {customID}) = S.singleton customID+-- componentIDS (C.LinkButton' _) = S.empty+-- componentIDS (C.Select' C.Select {customID}) = S.singleton customID+-- componentIDS (C.TextInput' C.TextInput {customID}) = S.singleton customID++-- | Generate a 'ViewInstance' of a 'View' by filling in 'CustomID's with random values+instantiateView :: RandomGen g => g -> View a -> (ViewInstance a, g)+instantiateView g v =+  case v of+    NilView x -> (ViewInstance (const $ ExtractOk SomeExtracted x) [], g)+    SingView f ->+      let (ViewComponent c p, g') = f g+          i = ViewInstance p [c]+       in (i, g')+    RowView x ->+      let (v'@ViewInstance {rendered}, g') = instantiateView g x+       in (v' {rendered = [C.ActionRow' rendered]}, g')+    MultView a b ->+      let (ViewInstance ia ra, g') = instantiateView g a+          (ViewInstance ib rb, g'') = instantiateView g' b+          inv i = ia i <*> ib i+       in (ViewInstance inv (ra <> rb), g'')++-- | Delete the initial message containing components+deleteInitialMsg :: (BotC r, P.Member (ViewEff a inp (Either e Message)) r) => P.Sem r ()+deleteInitialMsg = do+  ini <- getSendResponse+  case ini of+    Right m ->+      void . invoke $ DeleteMessage m m+    Left _ -> pure ()++-- | Run a 'View', returning the value passed to 'endView'+--+-- This function will not return until 'endView' is used inside the view.+-- If you want it to run in the background, consider using "Polysemy.Async".+--+-- This is async exception safe, you can use libraries such as+-- [polysemy-conc](https://hackage.haskell.org/package/polysemy-conc) to stop+-- views after a timeout.+runView ::+  BotC r =>+  -- | The initial view to render+  View inp ->+  -- | A function to send the rendered view (i.e. as a message or a modal)+  ([C.Component] -> P.Sem r sendResp) ->+  -- | Your callback effect.+  --+  -- local state semantics are preserved between calls here, you can keep state around+  (inp -> P.Sem (InteractionEff ': ViewEff a inp sendResp ': r) ()) ->+  P.Sem r a+runView v sendRendered m = do+  inst@ViewInstance {rendered} <- getStdRandom (`instantiateView` v)+  r <- sendRendered rendered+  runViewInstance r inst m++{- | Run a prerendered 'View', returning the value passed to 'endView'++ This function won't send the view, you should do that yourself+-}+runViewInstance ::+  BotC r =>+  -- | An initial value to act as the value of @GetSendResponse@+  --+  -- If you just sent a message, probably pass that+  sendResp ->+  -- | The initial view to run+  ViewInstance inp ->+  -- | Your callback effect.+  --+  -- In here you get access to the 'InteractionEff' and 'ViewEff' effects.+  --+  -- local state semantics are preserved between calls here, you can keep state around+  (inp -> P.Sem (InteractionEff ': ViewEff a inp sendResp ': r) ()) ->+  P.Sem r a+runViewInstance initSendResp inst m = P.resourceToIOFinal $ do+  eventIn <- P.embed STM.newTQueueIO++  P.bracket+    (P.raise $ react @'InteractionEvt (P.embed . sender eventIn))+    P.raise+    ( \_ -> do+        P.raise $ innerLoop initSendResp inst eventIn m+    )+  where+    interpretInteraction ::+      forall r.+      Interaction ->+      P.Sem (InteractionEff ': r) () ->+      P.Sem r ()+    interpretInteraction int =+      P.interpret+        ( \case+            GetInteraction -> pure int+        )++    interpretView ::+      forall r ret inp sendResp.+      P.Member (P.Embed IO) r =>+      P.Sem (ViewEff ret inp sendResp ': r) () ->+      P.Sem (P.State (Maybe ret, ViewInstance inp, sendResp) ': r) ()+    interpretView =+      P.reinterpretH+        ( \case+            EndView x -> P.modify' (_1 ?~ x) >>= P.pureT+            ReplaceView v m -> do+              inst@ViewInstance {rendered} <- P.embed $ getStdRandom (`instantiateView` v)+              P.modify' (_2 .~ inst)+              P.runTSimple $ m rendered+            GetSendResponse -> P.gets (^. _3) >>= P.pureT+        )++    innerLoop ::+      forall r ret inp sendResp.+      P.Members '[RatelimitEff, TokenEff, LogEff, MetricEff, P.Embed IO] r =>+      sendResp ->+      ViewInstance inp ->+      STM.TQueue Interaction ->+      (inp -> P.Sem (InteractionEff ': ViewEff ret inp sendResp ': r) ()) ->+      P.Sem r ret+    innerLoop initialSendResp initialInst inChan m = P.evalState (Nothing, initialInst, initialSendResp) innerLoop'+      where+        innerLoop' :: P.Sem (P.State (Maybe ret, ViewInstance inp, sendResp) ': r) ret+        innerLoop' = do+          (s, ViewInstance {extract}, _) <- P.get+          case s of+            Just x -> pure x+            Nothing -> do+              int <- P.embed $ STM.atomically (STM.readTQueue inChan)+              case extract int of+                ExtractOk SomeExtracted x -> interpretView $ interpretInteraction int (m x)+                _ -> pure ()++              -- if Just True == ((`S.member` customIDS) <$> extractCustomID int)+              --   then case extract int of+              --     ExtractOk SomeExtracted x -> interpretView $ interpretInteraction int (m x)+              --     _ -> pure ()+              --   else pure ()+              innerLoop'++    sender :: STM.TQueue Interaction -> Interaction -> IO ()+    sender eventIn int = STM.atomically $ STM.writeTQueue eventIn int
Calamity/Internal/AesonThings.hs view
@@ -1,32 +1,33 @@-module Calamity.Internal.AesonThings-    ( WithSpecialCases(..)-    , IfNoneThen-    , ExtractFieldFrom-    , ExtractFieldInto-    , ExtractFields-    , ExtractArrayField-    , DefaultToEmptyArray-    , DefaultToZero-    , DefaultToFalse-    , DefaultToTrue-    , CalamityJSON(..)-    , CalamityJSONKeepNothing(..)-    , jsonOptions-    , jsonOptionsKeepNothing ) where+module Calamity.Internal.AesonThings (+  WithSpecialCases (..),+  IfNoneThen,+  ExtractFieldFrom,+  ExtractFieldInto,+  ExtractFields,+  ExtractArrayField,+  DefaultToEmptyArray,+  DefaultToZero,+  DefaultToFalse,+  DefaultToTrue,+  CalamityJSON (..),+  CalamityJSONKeepNothing (..),+  jsonOptions,+  jsonOptionsKeepNothing,+) where -import           Control.Lens+import Control.Lens -import           Data.Aeson-import           Data.Aeson.Lens-import           Data.Aeson.Types      ( Parser )-import           Data.Kind-import           Data.Reflection       ( Reifies(..) )-import           Data.Typeable-import           Data.String           ( IsString(fromString) )+import Data.Aeson+import Data.Aeson.Lens+import Data.Aeson.Types (Parser)+import Data.Kind+import Data.Reflection (Reifies (..))+import Data.String (IsString (fromString))+import Data.Typeable -import           GHC.Generics-import           GHC.TypeLits          ( KnownSymbol, symbolVal )-import           Control.Monad ((>=>))+import Control.Monad ((>=>))+import GHC.Generics+import GHC.TypeLits (KnownSymbol, symbolVal)  textSymbolVal :: forall n s. (KnownSymbol n, IsString s) => s textSymbolVal = fromString $ symbolVal @n Proxy@@ -49,15 +50,18 @@ instance (KnownSymbol label, KnownSymbol field, KnownSymbol target) => PerformAction (ExtractFieldInto label field target) where   runAction _ o =     let v :: Maybe Value = o ^? ix (textSymbolVal @label) . _Object . ix (textSymbolVal @field)-    in pure $ o & at (textSymbolVal @target) .~ v+     in pure $ o & at (textSymbolVal @target) .~ v  instance PerformAction (ExtractFields label '[]) where   runAction _ = pure -instance (KnownSymbol field,-          PerformAction (ExtractFieldInto label field field),-          PerformAction (ExtractFields label fields)) =>-         PerformAction (ExtractFields label (field : fields)) where+instance+  ( KnownSymbol field+  , PerformAction (ExtractFieldInto label field field)+  , PerformAction (ExtractFields label fields)+  ) =>+  PerformAction (ExtractFields label (field : fields))+  where   runAction _ = runAction (Proxy @(ExtractFieldInto label field field)) >=> runAction (Proxy @(ExtractFields label fields))  instance (KnownSymbol label, KnownSymbol field) => PerformAction (ExtractArrayField label field) where@@ -85,13 +89,14 @@     o' <- runSpecialCases (Proxy @xs) o     runAction (Proxy @action) o' -instance (RunSpecialCase rules, Typeable a, Generic a, GFromJSON Zero (Rep a))-  => FromJSON (WithSpecialCases rules a) where+instance+  (RunSpecialCase rules, Typeable a, Generic a, GFromJSON Zero (Rep a)) =>+  FromJSON (WithSpecialCases rules a)+  where   parseJSON = withObject (show . typeRep $ Proxy @a) $ \o -> do     o' <- runSpecialCases (Proxy @rules) o     WithSpecialCases <$> genericParseJSON jsonOptions (Object o') - data DefaultToEmptyArray  instance Reifies DefaultToEmptyArray Value where@@ -138,12 +143,17 @@   parseJSON = fmap CalamityJSONKeepNothing . genericParseJSON jsonOptionsKeepNothing  jsonOptions :: Options-jsonOptions = defaultOptions { sumEncoding        = UntaggedValue-                             , fieldLabelModifier = camelTo2 '_' . filter (/= '_')-                             , omitNothingFields  = True }+jsonOptions =+  defaultOptions+    { sumEncoding = UntaggedValue+    , fieldLabelModifier = camelTo2 '_' . filter (/= '_')+    , omitNothingFields = True+    }  jsonOptionsKeepNothing :: Options-jsonOptionsKeepNothing = defaultOptions { sumEncoding        = UntaggedValue-                                        , fieldLabelModifier = camelTo2 '_' . filter (/= '_')-                                        , omitNothingFields  = False }-+jsonOptionsKeepNothing =+  defaultOptions+    { sumEncoding = UntaggedValue+    , fieldLabelModifier = camelTo2 '_' . filter (/= '_')+    , omitNothingFields = False+    }
Calamity/Internal/Updateable.hs view
@@ -72,7 +72,6 @@       & updateNullableDest @"application" n       & updateNullableDest @"messageReference" n       & mergeF @"flags" n-      & updateNullableDest @"stickers" n       & updateNullableDest @"referencedMessage" n       & updateNullableDest @"interaction" n       & mergeF @"components" n
Calamity/Internal/Utils.hs view
@@ -22,9 +22,10 @@   MaybeNull (..), ) where -import Calamity.Internal.RunIntoIO+-- import Calamity.Internal.RunIntoIO import Calamity.Types.LogEff import Control.Applicative+import Control.Monad (when) import Data.Aeson import Data.Aeson.Encoding (null_) import Data.Default.Class@@ -33,9 +34,9 @@ import Data.Text import qualified Data.Vector.Unboxing as VU import qualified DiPolysemy as Di+import GHC.Generics import qualified Polysemy as P import TextShow-import GHC.Generics import qualified TextShow.Generic as TSG  {- | Like whileM, but stateful effects are not preserved to mitigate memory leaks@@ -43,18 +44,16 @@  This means Polysemy.Error won't work to break the loop, etc.  Instead, Error/Alternative will just result in the loop quitting. -}-whileMFinalIO :: P.Member (P.Final IO) r => P.Sem r Bool -> P.Sem r ()+whileMFinalIO :: P.Sem r Bool -> P.Sem r () whileMFinalIO action = do-  action' <- runSemToIO action-  P.embedFinal $ go action'- where-  go action' = do-    r <- action'-    case r of-      Just True ->-        go action'-      _ ->-        pure ()+  go action+  where+    go action = do+      r <- action+      when r $ go_b action+    {-# INLINE go #-}+    go_b = go+    {-# NOINLINE go_b #-}  {- | Like untilJust, but stateful effects are not preserved to mitigate memory leaks @@ -63,16 +62,18 @@ -} untilJustFinalIO :: P.Member (P.Final IO) r => P.Sem r (Maybe a) -> P.Sem r a untilJustFinalIO action = do-  action' <- runSemToIO action-  P.embedFinal $ go action'- where-  go action' = do-    r <- action'-    case r of-      Just (Just a) ->-        pure a-      _ ->-        go action'+  go action+  where+    go action = do+      r <- action+      case r of+        Just a ->+          pure a+        _ ->+          go_b action+    {-# INLINE go #-}+    go_b = go+    {-# NOINLINE go_b #-}  whenJust :: Applicative m => Maybe a -> (a -> m ()) -> m () whenJust = flip $ maybe (pure ())@@ -148,10 +149,11 @@  newtype CalamityFromStringShow a = CalamityFromStringShow {unCalamityFromStringShow :: a}   deriving (FromJSON, ToJSON) via a-  deriving TextShow via FromStringShow a+  deriving (TextShow) via FromStringShow a --- | An alternative 'Maybe' type that allows us to distinguish between parsed--- json fields that were null, and fields that didn't exist.+{- | An alternative 'Maybe' type that allows us to distinguish between parsed+ json fields that were null, and fields that didn't exist.+-} data MaybeNull a   = WasNull   | NotNull a
Calamity/Types/Model/Channel.hs view
@@ -11,11 +11,13 @@   module Calamity.Types.Model.Channel.Embed,   module Calamity.Types.Model.Channel.ChannelType,   module Calamity.Types.Model.Channel.Message,+  module Calamity.Types.Model.Channel.Component, ) where  import Calamity.Internal.AesonThings import Calamity.Types.Model.Channel.Attachment import Calamity.Types.Model.Channel.ChannelType+import Calamity.Types.Model.Channel.Component import Calamity.Types.Model.Channel.DM import Calamity.Types.Model.Channel.Embed import Calamity.Types.Model.Channel.Group@@ -23,9 +25,8 @@ import Calamity.Types.Model.Channel.Message import Calamity.Types.Model.Channel.Reaction import Calamity.Types.Model.Channel.Webhook-import Calamity.Types.Partial+import Calamity.Types.Model.Guild.Permissions (Permissions) import Calamity.Types.Snowflake- import Data.Aeson import Data.Text (Text) import GHC.Generics@@ -59,6 +60,7 @@   { id :: Snowflake Channel   , name :: Text   , type_ :: !ChannelType+  , permissions :: Maybe Permissions   , parentID :: Maybe (Snowflake Category)   }   deriving (Show, Eq, Generic)
Calamity/Types/Model/Channel/Attachment.hs view
@@ -1,7 +1,8 @@ -- | Message attachments-module Calamity.Types.Model.Channel.Attachment (Attachment (..)) where+module Calamity.Types.Model.Channel.Attachment (+  Attachment (..),+) where -import Calamity.Internal.AesonThings () import Calamity.Types.Snowflake import Data.Aeson import Data.Text (Text)@@ -29,7 +30,7 @@   deriving (HasID Attachment) via HasIDField "id" Attachment  instance ToJSON Attachment where-  toJSON Attachment{id, filename, size, url, proxyUrl, dimensions = Just (width, height)} =+  toJSON Attachment {id, filename, size, url, proxyUrl, dimensions = Just (width, height)} =     object       [ "id" .= id       , "filename" .= filename@@ -39,7 +40,7 @@       , "width" .= width       , "height" .= height       ]-  toJSON Attachment{id, filename, size, url, proxyUrl} =+  toJSON Attachment {id, filename, size, url, proxyUrl} =     object ["id" .= id, "filename" .= filename, "size" .= size, "url" .= url, "proxy_url" .= proxyUrl]  instance FromJSON Attachment where@@ -50,3 +51,4 @@     Attachment <$> v .: "id" <*> v .: "filename" <*> v .: "size" <*> v .: "url" <*> v .: "proxy_url"       <*> pure         (fuseTup2 (width, height))+
Calamity/Types/Model/Channel/Component.hs view
@@ -1,120 +1,291 @@ -- | Message components module Calamity.Types.Model.Channel.Component (-    Component (..),-    Button (..),-    button,-    button',-    ButtonStyle (..),-    ComponentType (..),-    componentType,+  CustomID (..),+  Component (..),+  Button (..),+  LinkButton (..),+  button,+  button',+  lbutton,+  lbutton',+  ButtonStyle (..),+  Select (..),+  select,+  SelectOption (..),+  sopt,+  TextInput (..),+  TextInputStyle (..),+  textInput,+  ComponentType (..),+  componentType, ) where  import Calamity.Internal.AesonThings import Calamity.Types.Model.Guild.Emoji+import Control.Monad (replicateM) import Data.Aeson import Data.Scientific (toBoundedInteger) import qualified Data.Text as T import GHC.Generics+import System.Random (Uniform)+import System.Random.Stateful (Uniform (uniformM), UniformRange (uniformRM)) import TextShow import qualified TextShow.Generic as TSG +newtype CustomID = CustomID T.Text+  deriving stock (Eq, Ord, Show, Generic)+  deriving (TextShow) via TSG.FromGeneric CustomID+  deriving (ToJSON, FromJSON) via T.Text++instance Uniform CustomID where+  uniformM = ((CustomID . T.pack) <$>) . replicateM 16 . uniformRM ('a', 'z')+ data Button = Button-    { style :: ButtonStyle-    , label :: Maybe T.Text-    , emoji :: Maybe RawEmoji-    , customID :: Maybe T.Text-    , url :: Maybe T.Text-    , disabled :: Bool-    }-    deriving (Show, Generic)-    deriving (TextShow) via TSG.FromGeneric Button-    deriving (ToJSON) via CalamityJSONKeepNothing Button-    deriving-        (FromJSON)-        via WithSpecialCases-                '["disabled" `IfNoneThen` DefaultToFalse]-                Button+  { style :: ButtonStyle+  , label :: Maybe T.Text+  , emoji :: Maybe RawEmoji+  , disabled :: Bool+  , customID :: CustomID+  }+  deriving (Show, Generic)+  deriving (TextShow) via TSG.FromGeneric Button+  deriving (ToJSON) via CalamityJSONKeepNothing Button+  deriving+    (FromJSON)+    via WithSpecialCases+          '["disabled" `IfNoneThen` DefaultToFalse]+          Button +data LinkButton = LinkButton+  { stype :: ButtonStyle+  , label :: Maybe T.Text+  , emoji :: Maybe RawEmoji+  , url :: T.Text+  , disabled :: Bool+  }+  deriving (Show, Generic)+  deriving (TextShow) via TSG.FromGeneric LinkButton+  deriving (ToJSON) via CalamityJSONKeepNothing LinkButton+  deriving+    (FromJSON)+    via WithSpecialCases+          '["disabled" `IfNoneThen` DefaultToFalse]+          LinkButton+ data ButtonStyle-    = ButtonPrimary-    | ButtonSecondary-    | ButtonSuccess-    | ButtonDanger-    | ButtonLink-    deriving (Show, Generic)-    deriving (TextShow) via TSG.FromGeneric ButtonStyle+  = ButtonPrimary+  | ButtonSecondary+  | ButtonSuccess+  | ButtonDanger+  | ButtonLink+  deriving (Eq, Show, Generic)+  deriving (TextShow) via TSG.FromGeneric ButtonStyle -{- | Constuct a non-disabled 'Button' with the given 'ButtonStyle', all other- fields are set to 'Nothing'--}-button :: ButtonStyle -> Button-button s = Button s Nothing Nothing Nothing Nothing False+instance ToJSON ButtonStyle where+  toJSON t = toJSON @Int $ case t of+    ButtonPrimary -> 1+    ButtonSecondary -> 2+    ButtonSuccess -> 3+    ButtonDanger -> 4+    ButtonLink -> 5 -{- | Constuct a non-disabled 'Button' with the given 'ButtonStyle' and label,+instance FromJSON ButtonStyle where+  parseJSON = withScientific "ButtonStyle" $ \n -> case toBoundedInteger @Int n of+    Just v -> case v of+      1 -> pure ButtonPrimary+      2 -> pure ButtonSecondary+      3 -> pure ButtonSuccess+      4 -> pure ButtonDanger+      5 -> pure ButtonLink+      _ -> fail $ "Invalid ButtonStyle: " <> show n+    Nothing -> fail $ "Invalid ButtonStyle: " <> show n++{- | Constuct a non-disabled 'Button' with the given 'ButtonStyle' and 'CustomID',  all other fields are set to 'Nothing' -}-button' :: ButtonStyle -> T.Text -> Button-button' s l = Button s (Just l) Nothing Nothing Nothing False+button :: ButtonStyle -> CustomID -> Button+button s = Button s Nothing Nothing False -instance ToJSON ButtonStyle where-    toJSON t = toJSON @Int $ case t of-        ButtonPrimary -> 1-        ButtonSecondary -> 2-        ButtonSuccess -> 3-        ButtonDanger -> 4-        ButtonLink -> 5+{- | Constuct a non-disabled 'Button' with the given 'ButtonStyle', 'CustomID',+ and label, all other fields are set to 'Nothing'+-}+button' :: ButtonStyle -> T.Text -> CustomID -> Button+button' s l = Button s (Just l) Nothing False -instance FromJSON ButtonStyle where-    parseJSON = withScientific "ChannelType" $ \n -> case toBoundedInteger @Int n of-        Just v -> case v of-            1 -> pure ButtonPrimary-            2 -> pure ButtonSecondary-            3 -> pure ButtonSuccess-            4 -> pure ButtonDanger-            5 -> pure ButtonLink-            _ -> fail $ "Invalid ButtonStyle: " <> show n-        Nothing -> fail $ "Invalid ButtonStyle: " <> show n+{- | Constuct a non-disabled 'LinkButton' with the given 'ButtonStyle', link, all+   other fields are set to 'Nothing'+-}+lbutton ::+  ButtonStyle ->+  -- | The link to use+  T.Text ->+  LinkButton+lbutton s lnk = LinkButton s Nothing Nothing lnk False +{- | Constuct a non-disabled 'LinkButton' with the given 'ButtonStyle', link,+ and label, all other fields are set to 'Nothing'+-}+lbutton' ::+  ButtonStyle ->+  -- | The link to use+  T.Text ->+  -- | The label to use+  T.Text ->+  LinkButton+lbutton' s lnk lbl = LinkButton s (Just lbl) Nothing lnk False++data Select = Select+  { options :: [SelectOption]+  , placeholder :: Maybe T.Text+  , minValues :: Maybe Int+  , maxValues :: Maybe Int+  , disabled :: Bool+  , customID :: CustomID+  }+  deriving (Show, Generic)+  deriving (TextShow) via TSG.FromGeneric Select+  deriving (ToJSON) via CalamityJSONKeepNothing Select+  deriving+    (FromJSON)+    via WithSpecialCases+          '["disabled" `IfNoneThen` DefaultToFalse]+          Select++data SelectOption = SelectOption+  { label :: T.Text+  , value :: T.Text+  , description :: Maybe T.Text+  , emoji :: Maybe RawEmoji+  , default_ :: Bool+  }+  deriving (Show, Generic)+  deriving (TextShow) via TSG.FromGeneric SelectOption+  deriving (ToJSON) via CalamityJSONKeepNothing SelectOption+  deriving+    (FromJSON)+    via WithSpecialCases+          '["default" `IfNoneThen` DefaultToFalse]+          SelectOption++select :: [SelectOption] -> CustomID -> Select+select o = Select o Nothing Nothing Nothing False++sopt ::+  -- | Label+  T.Text ->+  -- | Value+  T.Text ->+  SelectOption+sopt l v = SelectOption l v Nothing Nothing False++data TextInput = TextInput+  { style :: TextInputStyle+  , label :: T.Text+  , minLength :: Maybe Int+  , maxLength :: Maybe Int+  , required :: Bool+  , value :: Maybe T.Text+  , placeholder :: Maybe T.Text+  , customID :: CustomID+  }+  deriving (Show, Generic)+  deriving (TextShow) via TSG.FromGeneric TextInput+  deriving (ToJSON) via CalamityJSONKeepNothing TextInput+  deriving+    (FromJSON)+    via WithSpecialCases+          '["required" `IfNoneThen` DefaultToFalse]+          TextInput++data TextInputStyle+  = TextInputShort+  | TextInputParagraph+  deriving (Show, Generic)+  deriving (TextShow) via TSG.FromGeneric TextInputStyle++instance ToJSON TextInputStyle where+  toJSON t = toJSON @Int $ case t of+    TextInputShort -> 1+    TextInputParagraph -> 2++instance FromJSON TextInputStyle where+  parseJSON = withScientific "TextInputStyle" $ \n -> case toBoundedInteger @Int n of+    Just v -> case v of+      1 -> pure TextInputShort+      2 -> pure TextInputParagraph+      _ -> fail $ "Invalid TextInputStyle: " <> show n+    Nothing -> fail $ "Invalid TextInputStyle: " <> show n++textInput ::+  TextInputStyle ->+  -- | Label+  T.Text ->+  CustomID ->+  TextInput+textInput s l = TextInput s l Nothing Nothing True Nothing Nothing+ data Component-    = Button' Button-    | ActionRow' [Component]-    deriving (Show, Generic)-    deriving (TextShow) via TSG.FromGeneric Component+  = ActionRow' [Component]+  | Button' Button+  | LinkButton' LinkButton+  | Select' Select+  | TextInput' TextInput+  deriving (Show, Generic)+  deriving (TextShow) via TSG.FromGeneric Component  instance ToJSON Component where-    toJSON t =-        let (Object inner, type_) = case t of-                ActionRow' xs -> (Object ("components" .= xs), 1 :: Int)-                Button' b -> (toJSON b, 2 :: Int)-         in Object (inner <> ("type" .= type_))+  toJSON t =+    let (Object inner, type_) = case t of+          ActionRow' xs -> (Object ("components" .= xs), 1 :: Int)+          Button' b -> (toJSON b, 2 :: Int)+          LinkButton' lb -> (toJSON lb, 2 :: Int)+          Select' s -> (toJSON s, 3 :: Int)+          TextInput' ti -> (toJSON ti, 4 :: Int)+     in Object (inner <> ("type" .= type_))  instance FromJSON Component where-    parseJSON = withObject "Component" $ \v -> do-        type_ :: Int <- v .: "type"+  parseJSON = withObject "Component" $ \v -> do+    type_ :: Int <- v .: "type" -        case type_ of-            1 -> do-                ActionRow' <$> v .: "components"-            2 -> Button' <$> parseJSON (Object v)-            _ -> fail $ "Invalid ComponentType: " <> show type_+    case type_ of+      1 -> ActionRow' <$> v .: "components"+      2 -> do+        cid :: Maybe CustomID <- v .:? "custom_id"+        url :: Maybe T.Text <- v .:? "url"+        case (cid, url) of+          (Just _, _) -> Button' <$> parseJSON (Object v)+          (_, Just _) -> LinkButton' <$> parseJSON (Object v)+          _ -> fail $ "Impossible button: " <> show v+      3 -> Select' <$> parseJSON (Object v)+      4 -> TextInput' <$> parseJSON (Object v)+      _ -> fail $ "Invalid ComponentType: " <> show type_  componentType :: Component -> ComponentType componentType (ActionRow' _) = ActionRowType componentType (Button' _) = ButtonType+componentType (LinkButton' _) = ButtonType+componentType (Select' _) = SelectType+componentType (TextInput' _) = TextInputType  data ComponentType-    = ActionRowType-    | ButtonType-    deriving (Show, Generic)-    deriving (TextShow) via TSG.FromGeneric ComponentType+  = ActionRowType+  | ButtonType+  | SelectType+  | TextInputType+  deriving (Eq, Show, Generic)+  deriving (TextShow) via TSG.FromGeneric ComponentType  instance ToJSON ComponentType where-    toJSON x = toJSON @Int $ case x of-        ActionRowType -> 1-        ButtonType -> 2+  toJSON x = toJSON @Int $ case x of+    ActionRowType -> 1+    ButtonType -> 2+    SelectType -> 3+    TextInputType -> 4  instance FromJSON ComponentType where-    parseJSON = withScientific "ComponentType" $ \n -> case toBoundedInteger @Int n of-        Just 1 -> pure ActionRowType-        Just 2 -> pure ButtonType-        _ -> fail $ "Invalid ComponentType: " <> show n+  parseJSON = withScientific "ComponentType" $ \n -> case toBoundedInteger @Int n of+    Just 1 -> pure ActionRowType+    Just 2 -> pure ButtonType+    Just 3 -> pure SelectType+    Just 4 -> pure TextInputType+    _ -> fail $ "Invalid ComponentType: " <> show n
− Calamity/Types/Model/Channel/Guild/Category.hs-boot
@@ -1,8 +0,0 @@-module Calamity.Types.Model.Channel.Guild.Category-    ( Category ) where---data Category--instance Show Category-instance Eq Category
Calamity/Types/Model/Channel/Guild/Text.hs view
@@ -6,8 +6,8 @@ import Calamity.Internal.SnowflakeMap (SnowflakeMap) import Calamity.Internal.Utils import {-# SOURCE #-} Calamity.Types.Model.Channel-import {-# SOURCE #-} Calamity.Types.Model.Channel.Guild.Category-import {-# SOURCE #-} Calamity.Types.Model.Channel.Message+import Calamity.Types.Model.Channel.Guild.Category+import Calamity.Types.Model.Channel.Message import {-# SOURCE #-} Calamity.Types.Model.Guild.Guild import Calamity.Types.Model.Guild.Overwrite import Calamity.Types.Snowflake
Calamity/Types/Model/Channel/Guild/Voice.hs view
@@ -5,7 +5,7 @@ import Calamity.Internal.SnowflakeMap (SnowflakeMap) import Calamity.Internal.Utils () import {-# SOURCE #-} Calamity.Types.Model.Channel-import {-# SOURCE #-} Calamity.Types.Model.Channel.Guild.Category+import Calamity.Types.Model.Channel.Guild.Category import {-# SOURCE #-} Calamity.Types.Model.Guild.Guild import Calamity.Types.Model.Guild.Overwrite import Calamity.Types.Snowflake
Calamity/Types/Model/Channel/Message.hs view
@@ -3,6 +3,7 @@   Message (..),   MessageType (..),   MessageReference (..),+  Partial (PartialMessage), ) where  import Calamity.Internal.AesonThings@@ -52,7 +53,6 @@   , application :: Maybe (CalamityFromStringShow Object)   , messageReference :: Maybe MessageReference   , flags :: Word64-  , stickers :: Maybe [CalamityFromStringShow Object]   , referencedMessage :: Maybe Message   , interaction :: Maybe (CalamityFromStringShow Object)   , components :: [Component]@@ -94,7 +94,6 @@   , application :: Maybe Object   , messageReference :: Maybe MessageReference   , flags :: Word64-  , stickers :: Maybe [Object]   , referencedMessage :: Maybe Message   , interaction :: Maybe Object   , components :: [Component]@@ -104,6 +103,15 @@   deriving (HasID Message) via HasIDField "id" Message   deriving (HasID Channel) via HasIDField "channelID" Message   deriving (HasID User) via HasIDField "author" Message++data instance Partial Message = PartialMessage+  { channelID :: Snowflake Channel+  , guildID :: Maybe (Snowflake Guild)+  }+  deriving (Show, Generic)+  deriving (TextShow) via TSG.FromGeneric (Partial Message)+  deriving (FromJSON) via CalamityJSON (Partial Message)+  deriving (HasID Channel) via HasIDField "channelID" (Partial Message)  data MessageReference = MessageReference   { messageID :: Maybe (Snowflake Message)
Calamity/Types/Model/Channel/UpdatedMessage.hs view
@@ -7,7 +7,6 @@ import Calamity.Internal.OverriddenVia import Calamity.Internal.Utils import Calamity.Types.Model.Channel-import Calamity.Types.Model.Channel.Component import Calamity.Types.Model.Guild.Role import Calamity.Types.Model.User import Calamity.Types.Snowflake@@ -40,7 +39,6 @@     , application :: Maybe (MaybeNull (CalamityFromStringShow Object))     , messageReference :: Maybe (MaybeNull MessageReference)     , flags :: Maybe Word64-    , stickers :: Maybe (MaybeNull [CalamityFromStringShow Object])     , referencedMessage :: Maybe (MaybeNull Message)     , interaction :: Maybe (MaybeNull (CalamityFromStringShow Object))     , components :: Maybe [Component]@@ -76,7 +74,6 @@     , application :: Maybe (MaybeNull Object)     , messageReference :: Maybe (MaybeNull MessageReference)     , flags :: Maybe Word64-    , stickers :: Maybe (MaybeNull [Object])     , referencedMessage :: Maybe (MaybeNull Message)     , interaction :: Maybe (MaybeNull Object)     , components :: Maybe [Component]
Calamity/Types/Model/Guild/Guild.hs view
@@ -112,14 +112,14 @@   parseJSON = withObject "Guild" $ \v -> do     id <- v .: "id" -    -- sadly we have now way of logging members/channels/presences' that failed to parser here     members' <- do       members' <- v .: "members"-      pure . SM.fromList . mapMaybe (parseMaybe @Object @Member (\m -> parseJSON $ Object (m <> "guild_id" .= id))) $ members'+      pure . SM.fromList . mapMaybe (parseMaybe @Object @Member (parseJSON . Object)) $ members' +    -- sadly we have now way of logging channels/presences' that failed to parse here     channels' <- do       channels' <- v .: "channels"-      pure . SM.fromList . mapMaybe (parseMaybe @Object @GuildChannel (\m -> parseJSON $ Object (m <> "guild_id" .= id))) $ channels'+      pure . SM.fromList . mapMaybe (parseMaybe @Object @GuildChannel (\c -> parseJSON $ Object (c <> "guild_id" .= id))) $ channels'      presences' <- do       presences' <- v .: "presences"@@ -158,7 +158,7 @@       <*> v .:? "system_channel_id"       <*> v .:? "joined_at"       <*> v .: "large"-      <*> v .: "unavailable"+      <*> v .:? "unavailable" .!= False       <*> v .: "member_count"       <*> v .: "voice_states"       <*> pure members'
Calamity/Types/Model/Guild/Member.hs view
@@ -4,7 +4,6 @@ import Calamity.Internal.AesonThings import Calamity.Internal.OverriddenVia import Calamity.Internal.Utils-import {-# SOURCE #-} Calamity.Types.Model.Guild.Guild import Calamity.Types.Model.Guild.Role import Calamity.Types.Model.User import Calamity.Types.Snowflake@@ -29,7 +28,6 @@   , email :: Maybe Text   , flags :: Maybe Word64   , premiumType :: Maybe Word64-  , guildID :: Snowflake Guild   , nick :: Maybe Text   , roles :: AesonVector (Snowflake Role)   , joinedAt :: CalamityFromStringShow UTCTime@@ -67,7 +65,6 @@   , email :: Maybe Text   , flags :: Maybe Word64   , premiumType :: Maybe Word64-  , guildID :: Snowflake Guild   , nick :: Maybe Text   , roles :: Vector (Snowflake Role)   , joinedAt :: UTCTime@@ -76,6 +73,5 @@   }   deriving (Eq, Show, Generic, NFData)   deriving (TextShow, FromJSON) via OverriddenVia Member Member'-  deriving (HasID Guild) via HasIDField "guildID" Member   deriving (HasID Member) via HasIDFieldCoerce "id" Member User   deriving (HasID User) via HasIDField "id" Member
Calamity/Types/Model/Guild/UnavailableGuild.hs view
@@ -1,23 +1,27 @@ -- | Guilds that are unavailable-module Calamity.Types.Model.Guild.UnavailableGuild-    ( UnavailableGuild(..) ) where+module Calamity.Types.Model.Guild.UnavailableGuild (UnavailableGuild (..)) where -import           Calamity.Internal.AesonThings-import           Calamity.Types.Model.Guild.Guild-import           Calamity.Types.Snowflake+import Calamity.Internal.AesonThings+import Calamity.Types.Model.Guild.Guild+import Calamity.Types.Snowflake -import           Data.Aeson+import Data.Aeson -import           GHC.Generics+import GHC.Generics -import           TextShow-import qualified TextShow.Generic                 as TSG+import TextShow+import qualified TextShow.Generic as TSG  data UnavailableGuild = UnavailableGuild-  { id          :: Snowflake Guild+  { id :: Snowflake Guild   , unavailable :: Bool   }-  deriving ( Eq, Show, Generic )-  deriving ( TextShow ) via TSG.FromGeneric UnavailableGuild-  deriving ( ToJSON, FromJSON ) via CalamityJSON UnavailableGuild-  deriving ( HasID Guild ) via HasIDField "id" UnavailableGuild+  deriving (Eq, Show, Generic)+  deriving (TextShow) via TSG.FromGeneric UnavailableGuild+  deriving (ToJSON) via CalamityJSON UnavailableGuild+  deriving+    (FromJSON)+    via WithSpecialCases+          '["disabled" `IfNoneThen` DefaultToFalse]+          UnavailableGuild+  deriving (HasID Guild) via HasIDField "id" UnavailableGuild
Calamity/Types/Model/Interaction.hs view
@@ -1,15 +1,18 @@ -- | Discord Interactions module Calamity.Types.Model.Interaction (-    Interaction (..),-    ApplicationCommandInteractionData (..),-    ApplicationCommandInteractionDataResolved (..),-    InteractionType (..),+  Interaction (..),+  InteractionToken (..),+  InteractionData (..),+  ResolvedInteractionData (..),+  InteractionType (..),+  Application,+  ApplicationCommand, ) where  import Calamity.Internal.AesonThings import Calamity.Internal.OverriddenVia import Calamity.Internal.Utils-import Calamity.Types.Model.Channel (Channel)+import Calamity.Types.Model.Channel (Attachment, Channel, Partial) import Calamity.Types.Model.Channel.Component import Calamity.Types.Model.Channel.Message (Message) import Calamity.Types.Model.Guild (Guild, Role)@@ -21,81 +24,91 @@ import Data.Scientific (toBoundedInteger) import qualified Data.Text as T import GHC.Generics-import TextShow-import qualified TextShow.Generic as TSG -data Interaction = Interaction-    { id :: Snowflake Interaction-    , applicationID :: Snowflake ()-    , type_ :: InteractionType-    , data_ :: Maybe ApplicationCommandInteractionData-    , guildID :: Maybe (Snowflake Guild)-    , channelID :: Maybe (Snowflake Channel)-    , member :: Maybe Member-    , user :: Maybe User-    , token :: T.Text-    , version :: Int-    , message :: Maybe Message-    }-    deriving (Show, Generic)-    deriving (TextShow) via TSG.FromGeneric Interaction-    deriving (FromJSON) via CalamityJSON Interaction+-- | Empty type to flag application IDs+data Application -data ApplicationCommandInteractionData = ApplicationCommandInteractionData-    { id :: Snowflake () -- no Command type yet-    , name :: T.Text-    , resolved :: Maybe ApplicationCommandInteractionDataResolved-    , -- , options :: [ApplicationCommandInteractionDataOptions]-      -- No commands yet-      customID :: T.Text-    , componentType :: ComponentType-    }-    deriving (Show, Generic)-    deriving (TextShow) via TSG.FromGeneric ApplicationCommandInteractionData-    deriving (FromJSON) via CalamityJSON ApplicationCommandInteractionData+-- | Empty type to flag application command IDs+data ApplicationCommand -data ApplicationCommandInteractionDataResolved' = ApplicationCommandInteractionDataResolved'-    { users :: CalamityFromStringShow (H.HashMap (Snowflake User) User)-    , members :: CalamityFromStringShow (H.HashMap (Snowflake Member) Member)-    , roles :: CalamityFromStringShow (H.HashMap (Snowflake Role) Role)-    , channels :: CalamityFromStringShow (H.HashMap (Snowflake Channel) Channel)-    }-    deriving (Generic)-    deriving (TextShow) via TSG.FromGeneric ApplicationCommandInteractionDataResolved'-    deriving (FromJSON) via CalamityJSON ApplicationCommandInteractionDataResolved'+newtype InteractionToken = InteractionToken+  { fromInteractionToken :: T.Text+  }+  deriving stock (Show, Generic)+  deriving (FromJSON, ToJSON) via T.Text -data ApplicationCommandInteractionDataResolved = ApplicationCommandInteractionDataResolved-    { users :: H.HashMap (Snowflake User) User-    , members :: H.HashMap (Snowflake Member) Member-    , roles :: H.HashMap (Snowflake Role) Role-    , channels :: H.HashMap (Snowflake Channel) Channel-    }-    deriving (Show, Generic)-    deriving-        (TextShow, FromJSON)-        via OverriddenVia ApplicationCommandInteractionDataResolved ApplicationCommandInteractionDataResolved'+data Interaction = Interaction+  { id :: Snowflake Interaction+  , applicationID :: Snowflake Application+  , type_ :: InteractionType+  , data_ :: Maybe InteractionData+  , guildID :: Maybe (Snowflake Guild)+  , channelID :: Maybe (Snowflake Channel)+  , member :: Maybe Member+  , user :: Maybe User+  , token :: InteractionToken+  , version :: Int+  , message :: Maybe Message+  , locale :: Maybe T.Text+  , guildLocale :: Maybe T.Text+  }+  deriving (Show, Generic)+  deriving (FromJSON) via CalamityJSON Interaction+  deriving (HasID Interaction) via HasIDField "id" Interaction+  deriving (HasID Application) via HasIDField "applicationID" Interaction -data InteractionType-    = PingType-    | ApplicationCommandType-    | MessageComponentType-    deriving (Show, Generic)-    deriving (TextShow) via TSG.FromGeneric InteractionType+data InteractionData = InteractionData+  { id :: Maybe (Snowflake ApplicationCommand)+  , name :: Maybe T.Text+  , resolved :: Maybe ResolvedInteractionData+  , -- , options :: [ApplicationCommandInteractionDataOptions]+    -- No commands yet+    customID :: Maybe CustomID+  , componentType :: Maybe ComponentType+  , values :: Maybe [T.Text]+  , targetID :: Maybe (Snowflake ())+  , components :: Maybe [Value]+  }+  deriving (Show, Generic)+  deriving (FromJSON) via CalamityJSON InteractionData --- instance ToJSON InteractionType where---     toJSON x = toJSON @Int $ case x of---         PingType -> 1---         ApplicationCommandType -> 2---         MessageComponentType -> 3+data ResolvedInteractionData' = ResolvedInteractionData'+  { users :: CalamityFromStringShow (H.HashMap (Snowflake User) User)+  , members :: CalamityFromStringShow (H.HashMap (Snowflake Member) Member)+  , roles :: CalamityFromStringShow (H.HashMap (Snowflake Role) Role)+  , channels :: CalamityFromStringShow (H.HashMap (Snowflake Channel) (Partial Channel))+  , messages :: CalamityFromStringShow (H.HashMap (Snowflake Message) (Partial Message))+  , attachments :: CalamityFromStringShow (H.HashMap (Snowflake Attachment) Attachment)+  }+  deriving (Generic)+  deriving (FromJSON) via CalamityJSON ResolvedInteractionData' ---     toEncoding x = toEncoding @Int $ case x of---         PingType -> 1---         ApplicationCommandType -> 2---         MessageComponentType -> 3+data ResolvedInteractionData = ResolvedInteractionData+  { users :: H.HashMap (Snowflake User) User+  , members :: H.HashMap (Snowflake Member) Member+  , roles :: H.HashMap (Snowflake Role) Role+  , channels :: H.HashMap (Snowflake Channel) (Partial Channel)+  , messages :: H.HashMap (Snowflake Message) (Partial Message)+  , attachments :: H.HashMap (Snowflake Attachment) Attachment+  }+  deriving (Show, Generic)+  deriving+    (FromJSON)+    via OverriddenVia ResolvedInteractionData ResolvedInteractionData' +data InteractionType+  = PingType+  | ApplicationCommandType+  | MessageComponentType+  | ApplicationCommandAutoCompleteType+  | ModalSubmitType+  deriving (Eq, Show, Generic)+ instance FromJSON InteractionType where-    parseJSON = withScientific "InteractionType" $ \n -> case toBoundedInteger @Int n of-        Just 1 -> pure PingType-        Just 2 -> pure ApplicationCommandType-        Just 3 -> pure MessageComponentType-        _ -> fail $ "Invalid InteractionType: " <> show n+  parseJSON = withScientific "InteractionType" $ \n -> case toBoundedInteger @Int n of+    Just 1 -> pure PingType+    Just 2 -> pure ApplicationCommandType+    Just 3 -> pure MessageComponentType+    Just 4 -> pure ApplicationCommandAutoCompleteType+    Just 5 -> pure ModalSubmitType+    _ -> fail $ "Invalid InteractionType: " <> show n
Calamity/Types/Tellable.hs view
@@ -1,45 +1,40 @@ -- | Things that are messageable module Calamity.Types.Tellable (-    ToMessage (..),-    Tellable (..),-    TFile (..),-    TMention (..),-    tell,-    reply,+  ToMessage (..),+  Tellable (..),+  TMention (..),+  tell,+  reply,+  runToMessage, ) where  import Calamity.Client.Types-import Calamity.HTTP+import Calamity.HTTP.Channel (+  AllowedMentions,+  ChannelRequest (CreateMessage),+  CreateMessageAttachment,+  CreateMessageOptions,+ )+import Calamity.HTTP.Internal.Request (invoke)+import Calamity.HTTP.Internal.Types (RestError)+import Calamity.HTTP.User (UserRequest (CreateDM)) import Calamity.Types.Model.Channel-import Calamity.Types.Model.Guild+import Calamity.Types.Model.Guild.Member (Member)+import Calamity.Types.Model.Guild.Role (Role) import Calamity.Types.Model.User import Calamity.Types.Snowflake- import Control.Lens--import Data.ByteString.Lazy (ByteString) import Data.Default.Class import Data.Monoid import qualified Data.Text as T import qualified Data.Text.Lazy as L- import GHC.Generics- import qualified Polysemy as P import qualified Polysemy.Error as P --- | A wrapper type for sending files-data TFile-    = TFile-        T.Text-        -- ^ The filename-        ByteString-        -- ^ The content-    deriving (Show, Generic)- -- | A wrapper type for allowing mentions newtype TMention a = TMention (Snowflake a)-    deriving (Show, Generic)+  deriving stock (Show, Generic)  {- | Things that can be used to send a message @@ -50,60 +45,102 @@  @ -} class ToMessage a where-    -- | Turn @a@ into a 'CreateMessageOptions' builder-    intoMsg :: a -> Endo CreateMessageOptions+  -- | Turn @a@ into a 'CreateMessageOptions' builder+  intoMsg :: a -> Endo CreateMessageOptions  -- | Message content, '(<>)' concatenates the content instance ToMessage L.Text where-    intoMsg t = Endo (#content %~ (<> Just (L.toStrict t)))+  intoMsg t = Endo (#content %~ (<> Just (L.toStrict t)))  -- | Message content, '(<>)' concatenates the content instance ToMessage T.Text where-    intoMsg t = Endo (#content %~ (<> Just t))+  intoMsg t = Endo (#content %~ (<> Just t))  -- | Message content, '(<>)' concatenates the content instance ToMessage String where-    intoMsg t = Endo (#content %~ (<> Just (T.pack t)))+  intoMsg t = Endo (#content %~ (<> Just (T.pack t))) --- | Message embed, '(<>)' merges embeds using '(<>)'+-- | Message embed, '(<>)' appends a new embed instance ToMessage Embed where-    intoMsg e = Endo (#embed %~ (<> Just e))+  intoMsg e = Endo (#embeds %~ (<> Just [e])) --- | Message file, '(<>)' keeps the last added file-instance ToMessage TFile where-    intoMsg (TFile n f) = Endo (#file %~ getLast . (<> Last (Just (n, f))) . Last)+-- | Message attachments, '(<>)' appends a new file+instance ToMessage CreateMessageAttachment where+  intoMsg a = Endo (#attachments %~ (<> Just [a]))  -- | Allowed mentions, '(<>)' combines allowed mentions instance ToMessage AllowedMentions where-    intoMsg m = Endo (#allowedMentions %~ (<> Just m))+  intoMsg m = Endo (#allowedMentions %~ (<> Just m))  -- | Add a 'User' id to the list of allowed user mentions instance ToMessage (TMention User) where-    intoMsg (TMention s) = intoMsg (def @AllowedMentions & #users <>~ [s])+  intoMsg (TMention s) = intoMsg (def @AllowedMentions & #users <>~ [s])  -- | Add a 'Member' id to the list of allowed user mentions instance ToMessage (TMention Member) where-    intoMsg (TMention s) = intoMsg (def @AllowedMentions & #users <>~ [coerceSnowflake s])+  intoMsg (TMention s) = intoMsg (def @AllowedMentions & #users <>~ [coerceSnowflake s])  -- | Add a 'Role' id to the list of allowed role mentions instance ToMessage (TMention Role) where-    intoMsg (TMention s) = intoMsg (def @AllowedMentions & #roles <>~ [s])+  intoMsg (TMention s) = intoMsg (def @AllowedMentions & #roles <>~ [s]) +fixupActionRow :: Component -> Component+fixupActionRow r@(ActionRow' _) = r+fixupActionRow x = ActionRow' [x]++{- | Add many components to a message.++ Each component will be wrapped in a singleton ActionRow if not already+-}+instance ToMessage [Component] where+  intoMsg c = Endo (#components %~ (<> Just (map fixupActionRow c)))++-- | Add an row of 'Button's to the message+instance ToMessage [Button] where+  intoMsg c = Endo (#components %~ (<> Just [ActionRow' . map Button' $ c]))++-- | Add an row of 'LinkButton's to the message+instance ToMessage [LinkButton] where+  intoMsg c = Endo (#components %~ (<> Just [ActionRow' . map LinkButton' $ c]))++-- | Add an row of 'Select's to the message+instance ToMessage [Select] where+  intoMsg c = Endo (#components %~ (<> Just [ActionRow' . map Select' $ c]))++{- | Add a singleton row containing a 'Component' to the message++ If the component is not already an actionrow, it is wrapped in a singleton row+-}+instance ToMessage Component where+  intoMsg c = Endo (#components %~ (<> Just [fixupActionRow c]))++-- | Add a singleton row containing a 'Button' to the message,+instance ToMessage Button where+  intoMsg c = Endo (#components %~ (<> Just [ActionRow' [Button' c]]))++-- | Add a singleton row containing a 'LinkButton' to the message,+instance ToMessage LinkButton where+  intoMsg c = Endo (#components %~ (<> Just [ActionRow' [LinkButton' c]]))++-- | Add a singleton row containing a 'Select' to the message,+instance ToMessage Select where+  intoMsg c = Endo (#components %~ (<> Just [ActionRow' [Select' c]]))+ -- | Set a 'MessageReference' as the message to reply to instance ToMessage MessageReference where-    intoMsg ref = Endo (#messageReference ?~ ref)+  intoMsg ref = Endo (#messageReference ?~ ref)  instance ToMessage (Endo CreateMessageOptions) where-    intoMsg = Prelude.id+  intoMsg = Prelude.id  instance ToMessage (CreateMessageOptions -> CreateMessageOptions) where-    intoMsg = Endo+  intoMsg = Endo  instance ToMessage CreateMessageOptions where-    intoMsg = Endo . const+  intoMsg = Endo . const  class Tellable a where-    getChannel :: (BotC r, P.Member (P.Error RestError) r) => a -> P.Sem r (Snowflake Channel)+  getChannel :: (BotC r, P.Member (P.Error RestError) r) => a -> P.Sem r (Snowflake Channel)  runToMessage :: ToMessage a => a -> CreateMessageOptions runToMessage = flip appEndo def . intoMsg@@ -123,9 +160,9 @@ -} tell :: forall msg r t. (BotC r, ToMessage msg, Tellable t) => t -> msg -> P.Sem r (Either RestError Message) tell target (runToMessage -> msg) = P.runError $ do-    cid <- getChannel target-    r <- invoke $ CreateMessage cid msg-    P.fromEither r+  cid <- getChannel target+  r <- invoke $ CreateMessage cid msg+  P.fromEither r  {- | Create a reply to an existing message in the same channel @@ -142,44 +179,44 @@ -} reply :: forall msg r t. (BotC r, ToMessage msg, HasID Channel t, HasID Message t) => t -> msg -> P.Sem r (Either RestError Message) reply target msg = P.runError $ do-    let msg' = runToMessage (intoMsg msg <> intoMsg (MessageReference (Just $ getID @Message target) (Just $ getID @Channel target) Nothing False))-    r <- invoke $ CreateMessage (getID @Channel target) msg'-    P.fromEither r+  let msg' = runToMessage (intoMsg msg <> intoMsg (MessageReference (Just $ getID @Message target) (Just $ getID @Channel target) Nothing False))+  r <- invoke $ CreateMessage (getID @Channel target) msg'+  P.fromEither r  instance Tellable DMChannel where-    getChannel = pure . getID+  getChannel = pure . getID  instance Tellable (Snowflake Channel) where-    getChannel = pure+  getChannel = pure  instance Tellable Channel where-    getChannel = pure . getID+  getChannel = pure . getID  instance Tellable (Snowflake DMChannel) where-    getChannel = pure . coerceSnowflake+  getChannel = pure . coerceSnowflake  instance Tellable TextChannel where-    getChannel = pure . getID+  getChannel = pure . getID  instance Tellable (Snowflake TextChannel) where-    getChannel = pure . coerceSnowflake+  getChannel = pure . coerceSnowflake  instance Tellable Message where-    getChannel = pure . getID+  getChannel = pure . getID  messageUser :: (BotC r, P.Member (P.Error RestError) r, HasID User a) => a -> P.Sem r (Snowflake Channel) messageUser (getID @User -> uid) = do-    c <- invoke $ CreateDM uid-    getID <$> P.fromEither c+  c <- invoke $ CreateDM uid+  getID <$> P.fromEither c  instance Tellable (Snowflake Member) where-    getChannel = messageUser . coerceSnowflake @_ @User+  getChannel = messageUser . coerceSnowflake @_ @User  instance Tellable Member where-    getChannel = messageUser+  getChannel = messageUser  instance Tellable User where-    getChannel = messageUser+  getChannel = messageUser  instance Tellable (Snowflake User) where-    getChannel = messageUser+  getChannel = messageUser
+ Calamity/Types/TokenEff.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE TemplateHaskell #-}++module Calamity.Types.TokenEff (+  TokenEff (..),+  getBotToken,+) where++import Calamity.Types.Token+import Polysemy++data TokenEff m a where+  GetBotToken :: TokenEff m Token++makeSem ''TokenEff
ChangeLog.md view
@@ -1,5 +1,33 @@ # Changelog for Calamity +## 0.3.0.0+++ The discord api version has been moved from v9 to v10.++ Added support for interactions and views with `Calamity.Interactions`.++ Some more effects have been put in, `RatelimitEff` and `TokenEff`, causing+  changes to `BotC`, `SetupEff`.++ The HTTP client can now be used without a `Client` instance.++ The `GuildMemberAdd` event now includes the guild id of the member.++ The `InteractionCreate` event has been added.++ Adding attachments to messages has now been reworked, and `TFile` has been+  replaced with `CreateMessageAttachment`.++ The `file` attribute of `CreateMessageOptions` has been removed and replaced+  with `attachments :: Maybe [CreateMessageAttachment]`.++ The `embed` attribute of `CreateMessageOptions` has been replaced with `embeds+  :: Maybe [Embed]`.++ The `components :: Maybe [Component]` field has been added to+  `CreateMessageOptions`.++ `editMessageEmbed` has been replaced with `editMessageEmbeds`.++ Added `Calamity.HTTP.Channel.editMessageComponents`.++ Added `Calamity.HTTP.Guild.SearchGuildMembers`.++ `Calamity.HTTP.Guild.GetGuildBans` now has a `GetGuildBansOptions` parameter.++ Added the module `Calamity.HTTP.Interaction`.++ Added the modules `Calamity.Interactions`,+  `Calamity.Interactions.{Eff,Utils,View}`.++ Fully fleshed out `Calamity.Types.Model.Channel.Component`.++ Removed `stickers` from `Message`.++ Removed the `guildID` field from `Member`.+ ## 0.2.0.2  + Dependency bump
README.md view
@@ -2,7 +2,7 @@  <p align="center">   <a href="https://hackage.haskell.org/package/calamity"><img src="https://img.shields.io/hackage/v/calamity" alt="Hackage"></a>-  <a href="https://gitlab.com/simmsb/calamity/pipelines"><img src="https://img.shields.io/gitlab/pipeline/simmsb/calamity" alt="Gitlab pipeline status"></a>+  <a href="https://github.com/simmsb/calamity/actions/workflows/build.yml"><img src="https://github.com/simmsb/calamity/actions/workflows/build.yml/badge.svg" alt="Build Status"></a>   <a href="https://github.com/simmsb/calamity/blob/master/LICENSE"><img src="https://img.shields.io/github/license/simmsb/calamity" alt="License"></a>   <a href="https://hackage.haskell.org/package/calamity"><img src="https://img.shields.io/hackage-deps/v/calamity" alt="Hackage-Deps"></a>   <a href="https://discord.gg/NGCThCY"><img src="https://discord.com/api/guilds/754446998077178088/widget.png?style=shield" alt="Discord Invite"></a>@@ -57,17 +57,17 @@ {- cabal:   build-depends:      base >= 4.13 && < 5-     , calamity >= 0.1.30.1-     , text >= 1.2 && < 2-     , lens >= 4.18 && < 5+     , calamity >= 0.3.0.0+     , text >= 1.2 && < 2.1+     , lens >= 5.1 && < 6      , di-polysemy ^>= 0.2      , di >= 1.3 && < 2      , df1 >= 0.3 && < 0.5      , di-core ^>= 1.0.4-     , polysemy ^>= 1.5-     , polysemy-plugin ^>= 0.3-     , stm ^>= 2.5-     , text-show ^>= 3.9+     , polysemy >= 1.5 && <2+     , polysemy-plugin >= 0.3 && <0.5+     , stm >= 2.5 && <3+     , text-show >= 3.8 && <4 -}  {-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}@@ -75,131 +75,151 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-}-{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-}  {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE BlockArguments #-} -{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE ApplicativeDo #-} -module Main where+module Main (main) where -import           Calamity-import           Calamity.Cache.InMemory+import Calamity+import Calamity.Cache.InMemory import Calamity.Commands-import Calamity.Commands.Context (FullContext, useFullContext)-import           Calamity.Metrics.Noop+import Calamity.Commands.Context (useFullContext)+import qualified Calamity.Interactions as I+import Calamity.Metrics.Noop+import Control.Concurrent+import Control.Lens+import Control.Monad+import qualified Data.Text as T+import qualified Di+import qualified DiPolysemy as DiP+import GHC.Generics+import qualified Polysemy as P+import qualified Polysemy.Async as P+import qualified Polysemy.State as P+import System.Environment (getEnv)+import TextShow -import           Control.Concurrent-import           Control.Concurrent.STM.TVar-import           Control.Lens-import           Control.Monad+data MyViewState = MyViewState+  { numOptions :: Int+  , selected :: Maybe T.Text+  }+  deriving (Generic) -import qualified Data.Text as T+main :: IO ()+main = do+  token <- T.pack <$> getEnv "BOT_TOKEN"+  Di.new $ \di ->+    void . P.runFinal . P.embedToFinal . DiP.runDiToIO di+      . runCacheInMemory+      . runMetricsNoop+      . useConstantPrefix "!"+      . useFullContext+      $ runBotIO (BotToken token) defaultIntents $ do+        addCommands $ do+          -- just some examples -import qualified Di-import qualified DiPolysemy                  as DiP+          command @'[User] "utest" \ctx u -> do+            void . tell @T.Text ctx $ "got user: " <> showt u+          command @'[Named "u" User, Named "u1" User] "utest2" \ctx u u1 -> do+            void . tell @T.Text ctx $ "got user: " <> showt u <> "\nand: " <> showt u1+          command @'[T.Text, Snowflake User] "test" \_ctx something aUser -> do+            DiP.info $ "something = " <> showt something <> ", aUser = " <> showt aUser+          group "testgroup" $ do+            void $ command @'[[T.Text]] "test" \ctx l -> do+              void . tell @T.Text ctx $ "you sent: " <> showt l+            group "say" do+              command @'[KleenePlusConcat T.Text] "this" \ctx msg -> do+                void $ tell @T.Text ctx msg+          command @'[] "explode" \_ctx -> do+            Just _ <- pure Nothing+            DiP.debug @T.Text "unreachable!"+          command @'[] "bye" \ctx -> do+            void $ tell @T.Text ctx "bye!"+            stopBot -import qualified Polysemy                    as P-import qualified Polysemy.Async              as P-import qualified Polysemy.AtomicState        as P-import qualified Polysemy.Embed              as P-import qualified Polysemy.Fail               as P+          -- views! -import           Prelude                     hiding ( error )+          command @'[] "components" \ctx -> do+            let view options = do+                  ~(add, done) <- I.row do+                    add <- I.button ButtonPrimary "add"+                    done <- I.button ButtonPrimary "done"+                    pure (add, done)+                  s <- I.select options+                  pure (add, done, s)+            let initialState = MyViewState 1 Nothing+            s <- P.evalState initialState $+              I.runView (view ["0"]) (tell ctx) \(add, done, s) -> do+                when add do+                  n <- P.gets (^. #numOptions)+                  let n' = n + 1+                  P.modify' (#numOptions .~ n')+                  let options = map (T.pack . show) [0 .. n]+                  I.replaceView (view options) (void . I.edit) -import           System.Environment          (getEnv)+                when done do+                  finalSelected <- P.gets (^. #selected)+                  I.endView finalSelected+                  I.deleteInitialMsg+                  void . I.respond $ case finalSelected of+                    Just x -> "Thanks: " <> x+                    Nothing -> "Oopsie" -import           TextShow+                case s of+                  Just s' -> do+                    P.modify' (#selected ?~ s')+                    void I.deferComponent+                  Nothing -> pure ()+            P.embed $ print s -data Counter m a where-  GetCounter :: Counter m Int+          -- more views! -P.makeSem ''Counter+          command @'[] "cresponses" \ctx -> do+            let view = I.row do+                  a <- I.button ButtonPrimary "defer"+                  b <- I.button ButtonPrimary "deferEph"+                  c <- I.button ButtonPrimary "deferComp"+                  d <- I.button ButtonPrimary "modal"+                  pure (a, b, c, d) -runCounterAtomic :: P.Member (P.Embed IO) r => P.Sem (Counter ': r) a -> P.Sem r a-runCounterAtomic m = do-  var <- P.embed $ newTVarIO (0 :: Int)-  P.runAtomicStateTVar var $ P.reinterpret (\case-                                              GetCounter -> P.atomicState (\v -> (v + 1, v))) m+                modalView = do+                  a <- I.textInput TextInputShort "a"+                  b <- I.textInput TextInputParagraph "b"+                  pure (a, b) -handleFailByLogging m = do-  r <- P.runFail m-  case r of-    Left e -> DiP.error $ T.pack e-    _      -> pure ()+            I.runView view (tell ctx) $ \(a, b, c, d) -> do+              when a do+                void I.defer+                P.embed $ threadDelay 1000000+                void $ I.followUp @T.Text "lol" -info, debug :: BotC r => T.Text -> P.Sem r ()-info = DiP.info-debug = DiP.info+              when b do+                void I.deferEphemeral+                P.embed $ threadDelay 1000000+                void $ I.followUpEphemeral @T.Text "lol" -tellt :: (BotC r, Tellable t) => t -> T.Text -> P.Sem r (Either RestError Message)-tellt t m = tell t $ T.toStrict m+              when c do+                void I.deferComponent+                P.embed $ threadDelay 1000000+                void $ I.followUp @T.Text "lol" -data MyCustomEvt = MyCustomEvt T.Text Message+              when d do+                void . P.async $ do+                  I.runView modalView (void . I.pushModal "lol") $ \(a, b) -> do+                    P.embed $ print (a, b)+                    void $ I.respond ("Thanks: " <> a <> " " <> b)+                    I.endView () -main :: IO ()-main = do-  token <- T.pack <$> getEnv "BOT_TOKEN"-  Di.new $ \di ->-    void . P.runFinal . P.embedToFinal . DiP.runDiToIO di . runCounterAtomic -         . runCacheInMemory . runMetricsNoop . useConstantPrefix "!" . useFullContext-      $ runBotIO (BotToken token) defaultIntents $ do-      addCommands $ do-        helpCommand-        command @'[User] "utest" $ \ctx u -> do-          void $ tellt ctx $ "got user: " <> showt u-        command @'[Named "u" User, Named "u1" User] "utest2" $ \ctx u u1 -> do-          void $ tellt ctx $ "got user: " <> showt u <> "\nand: " <> showt u1-        command @'[T.Text, Snowflake User] "test" $ \ctx something aUser -> do-          info $ "something = " <> showt something <> ", aUser = " <> showt aUser-        command @'[] "hello" $ \ctx -> do-          void $ tellt ctx "heya"-        group "testgroup" $ do-          command @'[[T.Text]] "test" $ \ctx l -> do-            void $ tellt ctx ("you sent: " <> showt l)-          command @'[] "count" $ \ctx -> do-            val <- getCounter-            void $ tellt ctx ("The value is: " <> showt val)-          group "say" $ do-            command @'[KleenePlusConcat T.Text] "this" $ \ctx msg -> do-              void $ tellt ctx msg-        command @'[Snowflake Emoji] "etest" $ \ctx e -> do-          void $ tellt ctx $ "got emoji: " <> showt e-        command @'[] "explode" $ \ctx -> do-          Just x <- pure Nothing-          debug "unreachable!"-        command @'[] "bye" $ \ctx -> do-          void $ tellt ctx "bye!"-          stopBot-        command @'[] "fire-evt" $ \ctx -> do-          fire . customEvt $ MyCustomEvt "aha" (ctx ^. #message)-        command @'[T.Text] "wait-for" $ \ctx s -> do-          void $ tellt ctx ("waiting for !" <> s)-          waitUntil @'MessageCreateEvt (\msg -> msg ^. #content == ("!" <> s))-          void $ tellt ctx ("got !" <> s)-      react @'MessageCreateEvt $ \msg -> handleFailByLogging $ case msg ^. #content of-        "!say hi" -> replicateM_ 3 . P.async $ do-          info "saying heya"-          Right msg' <- tellt msg "heya"-          info "sleeping"-          P.embed $ threadDelay (5 * 1000 * 1000)-          info "slept"-          void . invoke $ EditMessage (msg ^. #channelID) msg' (editMessageContent $ Just "lol")-          info "edited"-        _ -> pure ()-      react @('CustomEvt (CtxCommandError FullContext)) \(CtxCommandError ctx e) -> do-        info $ "Command failed with reason: " <> showt e-        case e of-          ParseError n r -> void . tellt ctx $ "Failed to parse parameter: `" <> T.fromStrict n <> "`, with reason: ```\n" <> r <> "```"-      react @('CustomEvt MyCustomEvt) $ \(MyCustomEvt s m) ->-        void $ tellt m ("Somebody told me to tell you about: " <> s)+              pure () ```  ## Disabling library logging@@ -225,3 +245,23 @@   -- ... ``` ++## Nix++If you trust me, I have a [cachix](https://www.cachix.org/) cache setup at+`simmsb-calamity`.++With cachix installed, you should be able to run `cachix use simmsb-calamity` to+add my cache to your list of caches.++You can also just manually add the substituter and public key:++```+substituters = https://simmsb-calamity.cachix.org+trusted-public-keys = simmsb-calamity.cachix.org-1:CQsXXpwKsjSVu0BJFT/JSvy1j6R7rMSW2r3cRQdcuQM= +```++After this nix builds should just use the cache (I hope?)++For an example of a bot built using nix, take a look at:+[simmsb/calamity-bot](https://github.com/simmsb/calamity-bot)
calamity.cabal view
@@ -1,11 +1,11 @@-cabal-version: 1.18+cabal-version: 2.0  -- This file has been generated from package.yaml by hpack version 0.34.4. -- -- see: https://github.com/sol/hpack  name:           calamity-version:        0.2.0.2+version:        0.3.0.0 synopsis:       A library for writing discord bots in haskell description:    Please see the README on GitHub at <https://github.com/simmsb/calamity#readme> category:       Network, Web@@ -55,6 +55,7 @@       Calamity.HTTP.Channel       Calamity.HTTP.Emoji       Calamity.HTTP.Guild+      Calamity.HTTP.Interaction       Calamity.HTTP.Internal.Ratelimit       Calamity.HTTP.Internal.Request       Calamity.HTTP.Internal.Route@@ -64,6 +65,10 @@       Calamity.HTTP.Reason       Calamity.HTTP.User       Calamity.HTTP.Webhook+      Calamity.Interactions+      Calamity.Interactions.Eff+      Calamity.Interactions.Utils+      Calamity.Interactions.View       Calamity.Internal.AesonThings       Calamity.Internal.BoundedStore       Calamity.Internal.ConstructorName@@ -120,13 +125,12 @@       Calamity.Types.Snowflake       Calamity.Types.Tellable       Calamity.Types.Token+      Calamity.Types.TokenEff       Calamity.Types.Upgradeable       Calamity.Utils       Calamity.Utils.Colour       Calamity.Utils.Message       Calamity.Utils.Permissions-  other-modules:-      Paths_calamity   hs-source-dirs:       ./   default-extensions:@@ -222,6 +226,8 @@     , stm-chans >=3.0 && <4     , stm-containers >=1.1 && <2     , text >=1.2 && <2.1+    -- text 2.0 usage is blocked on generics lens,+    -- it can be jailbroken by end users however     , text-show >=3.8 && <4     , time >=1.8 && <1.13     , tls >=1.4 && <2@@ -230,6 +236,7 @@     , unboxing-vector ==0.2.*     , unordered-containers ==0.2.*     , vector ==0.12.*-    , websockets ==0.12.*+    , websockets >=0.12 && <0.13     , x509-system >=1.6.6 && <1.7+    , random >=1.2 && <1.3   default-language: Haskell2010