packages feed

slack-web 1.6.1.0 → 2.0.1.0

raw patch · 58 files changed

+2977/−390 lines, 58 filesdep +cryptondep −cryptonitedep ~basedep ~servantdep ~servant-clientPVP ok

version bump matches the API change (PVP)

Dependencies added: crypton

Dependencies removed: cryptonite

Dependency ranges changed: base, servant, servant-client, servant-client-core

API changes (from Hackage documentation)

- Web.Slack.User: instance Data.Default.Class.Default Web.Slack.User.ListReq
- Web.Slack.UsersConversations: instance Data.Default.Class.Default Web.Slack.UsersConversations.UsersConversationsRequest
+ Web.Slack.Experimental.Blocks.Types: SlackSection :: Maybe SlackText -> Maybe SlackBlockId -> Maybe [SlackText] -> Maybe SlackAccessory -> SlackSection
+ Web.Slack.Experimental.Blocks.Types: [slackSectionAccessory] :: SlackSection -> Maybe SlackAccessory
+ Web.Slack.Experimental.Blocks.Types: [slackSectionBlockId] :: SlackSection -> Maybe SlackBlockId
+ Web.Slack.Experimental.Blocks.Types: [slackSectionFields] :: SlackSection -> Maybe [SlackText]
+ Web.Slack.Experimental.Blocks.Types: [slackSectionText] :: SlackSection -> Maybe SlackText
+ Web.Slack.Experimental.Blocks.Types: data SlackSection
+ Web.Slack.Experimental.Blocks.Types: divider :: SlackMessage
+ Web.Slack.Experimental.Blocks.Types: instance GHC.Classes.Eq Web.Slack.Experimental.Blocks.Types.SlackSection
+ Web.Slack.Experimental.Blocks.Types: instance GHC.Show.Show Web.Slack.Experimental.Blocks.Types.SlackConfirmObject
+ Web.Slack.Experimental.Blocks.Types: instance GHC.Show.Show Web.Slack.Experimental.Blocks.Types.SlackSection
+ Web.Slack.Experimental.Blocks.Types: slackContentToSlackText :: SlackContent -> Maybe SlackText
+ Web.Slack.Experimental.Blocks.Types: slackSectionWithText :: SlackText -> SlackSection
+ Web.Slack.Experimental.Events.Types: AttachmentField :: Text -> Text -> Maybe Bool -> AttachmentField
+ Web.Slack.Experimental.Events.Types: AttachmentMessageBlock :: TeamId -> ConversationId -> Text -> AttachmentMessageBlockMessage -> AttachmentMessageBlock
+ Web.Slack.Experimental.Events.Types: AttachmentMessageBlockMessage :: [SlackBlock] -> AttachmentMessageBlockMessage
+ Web.Slack.Experimental.Events.Types: BotMessageEvent :: Maybe [SlackBlock] -> ConversationId -> Text -> ChannelType -> Maybe [FileObject] -> Text -> Maybe Text -> Maybe Text -> Text -> Maybe [MessageAttachment] -> BotMessageEvent
+ Web.Slack.Experimental.Events.Types: DecodedMessageAttachment :: Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe [AttachmentField] -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Bool -> Maybe [AttachmentMessageBlock] -> Maybe UserId -> Maybe ConversationId -> Maybe TeamId -> Maybe Bool -> Maybe Text -> Maybe Text -> DecodedMessageAttachment
+ Web.Slack.Experimental.Events.Types: EventBotMessage :: BotMessageEvent -> Event
+ Web.Slack.Experimental.Events.Types: MessageAttachment :: Maybe DecodedMessageAttachment -> Value -> MessageAttachment
+ Web.Slack.Experimental.Events.Types: [$sel:appId:BotMessageEvent] :: BotMessageEvent -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:appUnfurlUrl:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:attachments:BotMessageEvent] :: BotMessageEvent -> Maybe [MessageAttachment]
+ Web.Slack.Experimental.Events.Types: [$sel:attachments:MessageEvent] :: MessageEvent -> Maybe [MessageAttachment]
+ Web.Slack.Experimental.Events.Types: [$sel:authorIcon:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:authorId:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe UserId
+ Web.Slack.Experimental.Events.Types: [$sel:authorLink:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:authorName:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:blocks:AttachmentMessageBlockMessage] :: AttachmentMessageBlockMessage -> [SlackBlock]
+ Web.Slack.Experimental.Events.Types: [$sel:blocks:BotMessageEvent] :: BotMessageEvent -> Maybe [SlackBlock]
+ Web.Slack.Experimental.Events.Types: [$sel:botId:BotMessageEvent] :: BotMessageEvent -> Text
+ Web.Slack.Experimental.Events.Types: [$sel:channel:AttachmentMessageBlock] :: AttachmentMessageBlock -> ConversationId
+ Web.Slack.Experimental.Events.Types: [$sel:channel:BotMessageEvent] :: BotMessageEvent -> ConversationId
+ Web.Slack.Experimental.Events.Types: [$sel:channelId:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe ConversationId
+ Web.Slack.Experimental.Events.Types: [$sel:channelTeam:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe TeamId
+ Web.Slack.Experimental.Events.Types: [$sel:channelType:BotMessageEvent] :: BotMessageEvent -> ChannelType
+ Web.Slack.Experimental.Events.Types: [$sel:color:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:decoded:MessageAttachment] :: MessageAttachment -> Maybe DecodedMessageAttachment
+ Web.Slack.Experimental.Events.Types: [$sel:fallback:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:fields:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe [AttachmentField]
+ Web.Slack.Experimental.Events.Types: [$sel:files:BotMessageEvent] :: BotMessageEvent -> Maybe [FileObject]
+ Web.Slack.Experimental.Events.Types: [$sel:footer:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:footerIcon:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:fromUrl:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:imageUrl:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:isAppUnfurl:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Bool
+ Web.Slack.Experimental.Events.Types: [$sel:isMsgUnfurl:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Bool
+ Web.Slack.Experimental.Events.Types: [$sel:message:AttachmentMessageBlock] :: AttachmentMessageBlock -> AttachmentMessageBlockMessage
+ Web.Slack.Experimental.Events.Types: [$sel:messageBlocks:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe [AttachmentMessageBlock]
+ Web.Slack.Experimental.Events.Types: [$sel:pretext:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:raw:MessageAttachment] :: MessageAttachment -> Value
+ Web.Slack.Experimental.Events.Types: [$sel:short:AttachmentField] :: AttachmentField -> Maybe Bool
+ Web.Slack.Experimental.Events.Types: [$sel:team:AttachmentMessageBlock] :: AttachmentMessageBlock -> TeamId
+ Web.Slack.Experimental.Events.Types: [$sel:text:BotMessageEvent] :: BotMessageEvent -> Text
+ Web.Slack.Experimental.Events.Types: [$sel:text:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:threadTs:BotMessageEvent] :: BotMessageEvent -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:thumbUrl:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:title:AttachmentField] :: AttachmentField -> Text
+ Web.Slack.Experimental.Events.Types: [$sel:title:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:titleLink:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:ts:AttachmentMessageBlock] :: AttachmentMessageBlock -> Text
+ Web.Slack.Experimental.Events.Types: [$sel:ts:BotMessageEvent] :: BotMessageEvent -> Text
+ Web.Slack.Experimental.Events.Types: [$sel:ts:DecodedMessageAttachment] :: DecodedMessageAttachment -> Maybe Text
+ Web.Slack.Experimental.Events.Types: [$sel:value:AttachmentField] :: AttachmentField -> Text
+ Web.Slack.Experimental.Events.Types: data AttachmentField
+ Web.Slack.Experimental.Events.Types: data AttachmentMessageBlock
+ Web.Slack.Experimental.Events.Types: data AttachmentMessageBlockMessage
+ Web.Slack.Experimental.Events.Types: data BotMessageEvent
+ Web.Slack.Experimental.Events.Types: data DecodedMessageAttachment
+ Web.Slack.Experimental.Events.Types: data MessageAttachment
+ Web.Slack.Experimental.Events.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.Slack.Experimental.Events.Types.AttachmentField
+ Web.Slack.Experimental.Events.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.Slack.Experimental.Events.Types.AttachmentMessageBlock
+ Web.Slack.Experimental.Events.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.Slack.Experimental.Events.Types.AttachmentMessageBlockMessage
+ Web.Slack.Experimental.Events.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.Slack.Experimental.Events.Types.BotMessageEvent
+ Web.Slack.Experimental.Events.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.Slack.Experimental.Events.Types.DecodedMessageAttachment
+ Web.Slack.Experimental.Events.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.Slack.Experimental.Events.Types.MessageAttachment
+ Web.Slack.Experimental.Events.Types: instance GHC.Show.Show Web.Slack.Experimental.Events.Types.AttachmentField
+ Web.Slack.Experimental.Events.Types: instance GHC.Show.Show Web.Slack.Experimental.Events.Types.AttachmentMessageBlock
+ Web.Slack.Experimental.Events.Types: instance GHC.Show.Show Web.Slack.Experimental.Events.Types.AttachmentMessageBlockMessage
+ Web.Slack.Experimental.Events.Types: instance GHC.Show.Show Web.Slack.Experimental.Events.Types.BotMessageEvent
+ Web.Slack.Experimental.Events.Types: instance GHC.Show.Show Web.Slack.Experimental.Events.Types.DecodedMessageAttachment
+ Web.Slack.Experimental.Events.Types: instance GHC.Show.Show Web.Slack.Experimental.Events.Types.MessageAttachment
+ Web.Slack.User: instance Data.Default.Internal.Default Web.Slack.User.ListReq
+ Web.Slack.UsersConversations: instance Data.Default.Internal.Default Web.Slack.UsersConversations.UsersConversationsRequest
- Web.Slack.Experimental.Blocks: SlackBlockSection :: SlackText -> Maybe SlackAccessory -> SlackBlock
+ Web.Slack.Experimental.Blocks: SlackBlockSection :: SlackSection -> SlackBlock
- Web.Slack.Experimental.Blocks.Types: SlackBlockSection :: SlackText -> Maybe SlackAccessory -> SlackBlock
+ Web.Slack.Experimental.Blocks.Types: SlackBlockSection :: SlackSection -> SlackBlock
- Web.Slack.Experimental.Events.Types: MessageEvent :: Maybe [SlackBlock] -> ConversationId -> Text -> ChannelType -> Maybe [FileObject] -> UserId -> Text -> Maybe Text -> Maybe Text -> Maybe Text -> MessageEvent
+ Web.Slack.Experimental.Events.Types: MessageEvent :: Maybe [SlackBlock] -> ConversationId -> Text -> ChannelType -> Maybe [FileObject] -> UserId -> Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe [MessageAttachment] -> MessageEvent

Files

CHANGELOG.md view
@@ -1,3 +1,30 @@+# 2.0.1.0 (2025-01-09)+* [#136](https://github.com/MercuryTechnologies/slack-web/pull/136)+  Bumps the version ranges for dependencies to be compatible with LTS 22.43.++# 2.0.0.4 (2024-09-15)+* [#135](https://github.com/MercuryTechnologies/slack-web/pull/135)+  Improves attachement support by providing clients wih the raw JSON value+  in case of a parse failure.++# 2.0.0.3 (2024-08-30)+* [#133](https://github.com/MercuryTechnologies/slack-web/pull/133)+  Adds attachment support for message event subscriptions.++# 2.0.0.2 (2024-08-29)+* [#132](https://github.com/MercuryTechnologies/slack-web/pull/132)+  Add support for bot_message event subtype.++# 2.0.0.0 (2023-10-26)++* [#123](https://github.com/MercuryTechnologies/slack-web/pull/123)+  Add support for section fields.++# 1.6.2.0 (2023-10-24)++* [#127](https://github.com/MercuryTechnologies/slack-web/pull/127)+  Add helper for the divider block type.+ # 1.6.1.0 (2022-12-16)  * [#124](https://github.com/MercuryTechnologies/slack-web/pull/124)
main/cli.hs view
@@ -64,11 +64,12 @@      addCmd "conversations.history" $ do       conversationId <--        Slack.ConversationId . Text.pack+        Slack.ConversationId+          . Text.pack           <$> addParamString "CONVERSATION_ID" (paramHelpStr "ID of the conversation to fetch")       getsAll <- addSimpleBoolFlag "A" ["all"] (flagHelpStr "Get all available messages in the channel")-      addCmdImpl $-        if getsAll+      addCmdImpl+        $ if getsAll           then do             (`runReaderT` apiConfig) $ do               fetchPage <- Slack.conversationsHistoryAll $ (SlackConversation.mkHistoryReq conversationId) {SlackConversation.historyReqCount = 2}@@ -101,7 +102,8 @@      addCmd "conversations.replies" $ do       conversationId <--        Slack.ConversationId . Text.pack+        Slack.ConversationId+          . Text.pack           <$> addParamString "CONVERSATION_ID" (paramHelpStr "ID of the conversation to fetch")       threadTimeStampStr <- addParamString "TIMESTAMP" (paramHelpStr "Timestamp of the thread to fetch")       let ethreadTimeStamp = Slack.timestampFromText $ Text.pack threadTimeStampStr
slack-web.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: slack-web-version: 1.6.1.0+version: 2.0.1.0  build-type: Simple @@ -32,7 +32,7 @@  category: Web -tested-with: GHC == 8.10.7 || ==9.2.4 || == 9.4.2+tested-with: GHC == 8.10.7 || ==9.2.4 || == 9.4.2 || == 9.6.6  extra-source-files:   CHANGELOG.md@@ -133,12 +133,12 @@       Web.Slack.AesonUtils   build-depends:       aeson >= 2.0 && < 2.2-    , base >= 4.11 && < 4.18+    , base >= 4.11 && < 4.19     , base16-bytestring     , bytestring     , classy-prelude     , containers-    , cryptonite+    , crypton     , data-default-class     , deepseq     , either@@ -152,9 +152,9 @@     , mtl     , refined     , scientific-    , servant >= 0.16 && < 0.20-    , servant-client >= 0.16 && < 0.20-    , servant-client-core >= 0.16 && < 0.20+    , servant >= 0.16 && < 0.21+    , servant-client >= 0.16 && < 0.21+    , servant-client-core >= 0.16 && < 0.21     , string-conversions     , string-variants >= 0.1.0.1     , text (>= 1.2 && < 1.3) || (>= 2.0 && < 2.1)@@ -186,6 +186,7 @@       Web.Slack.Experimental.RequestVerificationSpec       Web.Slack.Experimental.Events.TypesSpec       Web.Slack.Experimental.BlocksSpec+      Web.Slack.Experimental.Blocks.TypesSpec       TestImport       TestImport.Aeson   build-tool-depends:@@ -205,6 +206,7 @@     , mtl     , pretty-simple ^>= 4.1     , quickcheck-instances+    , refined     , slack-web     , string-conversions     , string-variants
src/Web/Slack.hs view
@@ -12,34 +12,33 @@ -- *Since 0.4.0.0*: The API functions is now more intuitive for newbies -- than before. If you need compatiblity with the previous version, use -- 'Web.Slack.Classy' instead.-module Web.Slack-  ( SlackConfig (..),-    mkSlackConfig,+module Web.Slack (+  SlackConfig (..),+  mkSlackConfig, -    -- * Endpoints-    apiTest,-    authTest,-    chatPostMessage,-    chatUpdate,-    conversationsList,-    conversationsListAll,-    conversationsHistory,-    conversationsHistoryAll,-    conversationsReplies,-    repliesFetchAll,-    getUserDesc,-    usersList,-    usersListAll,-    userLookupByEmail,-    UsersConversations.usersConversations,-    UsersConversations.usersConversationsAll,+  -- * Endpoints+  apiTest,+  authTest,+  chatPostMessage,+  chatUpdate,+  conversationsList,+  conversationsListAll,+  conversationsHistory,+  conversationsHistoryAll,+  conversationsReplies,+  repliesFetchAll,+  getUserDesc,+  usersList,+  usersListAll,+  userLookupByEmail,+  UsersConversations.usersConversations,+  UsersConversations.usersConversationsAll, -    -- * Requests and responses-    authenticateReq,-    Response,-    LoadPage,-  )-where+  -- * Requests and responses+  authenticateReq,+  Response,+  LoadPage,+) where  -- FIXME: Web.Slack.Prelude 
src/Web/Slack/AesonUtils.hs view
@@ -76,13 +76,13 @@ objectOptional = J.object . catMaybes  -- | Encode a value for 'objectOptional'-(.=!) :: ToJSON v => Key -> v -> Maybe Pair+(.=!) :: (ToJSON v) => Key -> v -> Maybe Pair key .=! val = Just (key .= val)  infixr 8 .=!  -- | Encode a Maybe value for 'objectOptional'-(.=?) :: ToJSON v => Key -> Maybe v -> Maybe Pair+(.=?) :: (ToJSON v) => Key -> Maybe v -> Maybe Pair key .=? mVal = fmap (key .=) mVal  infixr 8 .=?
src/Web/Slack/Api.hs view
@@ -9,12 +9,11 @@ -- | -- Module: Web.Slack.Api -- Description:-module Web.Slack.Api-  ( TestReq (..),-    mkTestReq,-    TestRsp (..),-  )-where+module Web.Slack.Api (+  TestReq (..),+  mkTestReq,+  TestRsp (..),+) where  -- FIXME: Web.Slack.Prelude 
src/Web/Slack/Chat.hs view
@@ -2,16 +2,15 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} -module Web.Slack.Chat-  ( PostMsg (..),-    PostMsgReq (..),-    mkPostMsgReq,-    PostMsgRsp (..),-    UpdateReq (..),-    mkUpdateReq,-    UpdateRsp (..),-  )-where+module Web.Slack.Chat (+  PostMsg (..),+  PostMsgReq (..),+  mkPostMsgReq,+  PostMsgRsp (..),+  UpdateReq (..),+  mkUpdateReq,+  UpdateRsp (..),+) where  import Web.FormUrlEncoded import Web.Slack.Conversation (ConversationId)
src/Web/Slack/Classy.hs view
@@ -5,27 +5,26 @@ -- | -- Module: Web.Slack.Classy -- Description: For compatibility with Web.Slack prior to v0.4.0.0.-module Web.Slack.Classy-  ( SlackConfig (..),-    mkSlackConfig,-    apiTest,-    authTest,-    chatPostMessage,-    conversationsList,-    conversationsHistory,-    conversationsHistoryAll,-    conversationsReplies,-    repliesFetchAll,-    getUserDesc,-    usersList,-    userLookupByEmail,-    authenticateReq,-    Response,-    LoadPage,-    HasManager (..),-    HasToken (..),-  )-where+module Web.Slack.Classy (+  SlackConfig (..),+  mkSlackConfig,+  apiTest,+  authTest,+  chatPostMessage,+  conversationsList,+  conversationsHistory,+  conversationsHistoryAll,+  conversationsReplies,+  repliesFetchAll,+  getUserDesc,+  usersList,+  userLookupByEmail,+  authenticateReq,+  Response,+  LoadPage,+  HasManager (..),+  HasToken (..),+) where  import Data.Map qualified as Map import Network.HTTP.Client (Manager)
src/Web/Slack/Common.hs view
@@ -15,21 +15,20 @@ -- | -- Module: Web.Slack.Common -- Description:-module Web.Slack.Common-  ( Color (..),-    UserId (..),-    ConversationId (..),-    TeamId (..),-    Cursor (..),-    SlackTimestamp (..),-    mkSlackTimestamp,-    timestampFromText,-    Message (..),-    MessageType (..),-    SlackClientError (..),-    SlackMessageText (..),-  )-where+module Web.Slack.Common (+  Color (..),+  UserId (..),+  ConversationId (..),+  TeamId (..),+  Cursor (..),+  SlackTimestamp (..),+  mkSlackTimestamp,+  timestampFromText,+  Message (..),+  MessageType (..),+  SlackClientError (..),+  SlackMessageText (..),+) where  -- FIXME: Web.Slack.Prelude 
src/Web/Slack/Conversation.hs view
@@ -15,27 +15,26 @@ -- | -- Module: Web.Slack.Channel -- Description: Types and functions related to <https://api.slack.com/docs/conversations-api Conversation API>-module Web.Slack.Conversation-  ( Conversation (..),-    ConversationId (..),-    ConversationType (..),-    ChannelConversation (..),-    GroupConversation (..),-    ImConversation (..),-    TeamId (..),-    Purpose (..),-    Topic (..),-    ListReq (..),-    mkListReq,-    ListRsp (..),-    HistoryReq (..),-    mkHistoryReq,-    HistoryRsp (..),-    RepliesReq (..),-    mkRepliesReq,-    ResponseMetadata (..),-  )-where+module Web.Slack.Conversation (+  Conversation (..),+  ConversationId (..),+  ConversationType (..),+  ChannelConversation (..),+  GroupConversation (..),+  ImConversation (..),+  TeamId (..),+  Purpose (..),+  Topic (..),+  ListReq (..),+  mkListReq,+  ListRsp (..),+  HistoryReq (..),+  mkHistoryReq,+  HistoryRsp (..),+  RepliesReq (..),+  mkRepliesReq,+  ResponseMetadata (..),+) where  import Data.Aeson import Data.Aeson.Encoding@@ -209,7 +208,7 @@       -- This uses the outer Parser monad since deciding which parser to use       -- is monadic, then the Maybe to decide which parser is picked, then       -- finally the inner parser to actually run it-      parseWhen :: FromJSON a => Key -> (a -> b) -> Object -> Parser (Maybe (Parser b))+      parseWhen :: (FromJSON a) => Key -> (a -> b) -> Object -> Parser (Maybe (Parser b))       parseWhen key con o = do         -- Slack only inconsistently includes the is_* attributes if false.         is <- o .:? key .!= False
src/Web/Slack/Experimental/Blocks.hs view
@@ -1,79 +1,78 @@-module Web.Slack.Experimental.Blocks-  ( -- * General Slack Messages-    SlackText,-    (<+>),-    parens,-    brackets,-    angleBrackets,-    ticks,-    codeBlock,-    bold,-    italic,-    newline,-    unorderedList,-    link,-    monospaced,-    mentionUser,-    isSubStringOf,-    SlackImage (..),-    SlackMessage,-    Markdown (..),-    Image (..),-    context,-    textToMessage,-    prefixFirstSlackMessage,-    mentionUserGroupById,-    textToContext,-    slackMessage,-    SlackBlock (..),+module Web.Slack.Experimental.Blocks (+  -- * General Slack Messages+  SlackText,+  (<+>),+  parens,+  brackets,+  angleBrackets,+  ticks,+  codeBlock,+  bold,+  italic,+  newline,+  unorderedList,+  link,+  monospaced,+  mentionUser,+  isSubStringOf,+  SlackImage (..),+  SlackMessage,+  Markdown (..),+  Image (..),+  context,+  textToMessage,+  prefixFirstSlackMessage,+  mentionUserGroupById,+  textToContext,+  slackMessage,+  SlackBlock (..), -    -- ** Blocks' rich text formatting (receive only!)-    RichItem (..),-    RichStyle (..),-    RichLinkAttrs (..),-    RichTextSectionItem (..),-    RichText (..),+  -- ** Blocks' rich text formatting (receive only!)+  RichItem (..),+  RichStyle (..),+  RichLinkAttrs (..),+  RichTextSectionItem (..),+  RichText (..), -    -- * Rendered messages-    RenderedSlackMessage (..),-    render,+  -- * Rendered messages+  RenderedSlackMessage (..),+  render, -    -- * Introduction to Slack Interactive Messages-    -- $interactive+  -- * Introduction to Slack Interactive Messages+  -- $interactive -    -- * Creating Slack Interactive Messages-    actions,-    actionsWithBlockId,-    SlackActionId (..),-    SlackBlockId,-    setting,-    emptySetting,-    SlackStyle (..),-    plaintext,-    plaintextonly,-    mrkdwn,-    button,-    buttonSettings,-    ButtonSettings-      ( buttonUrl,-        buttonValue,-        buttonStyle,-        buttonConfirm-      ),-    confirm,-    confirmAreYouSure,-    ConfirmSettings-      ( confirmTitle,-        confirmText,-        confirmConfirm,-        confirmDeny,-        confirmStyle-      ),+  -- * Creating Slack Interactive Messages+  actions,+  actionsWithBlockId,+  SlackActionId (..),+  SlackBlockId,+  setting,+  emptySetting,+  SlackStyle (..),+  plaintext,+  plaintextonly,+  mrkdwn,+  button,+  buttonSettings,+  ButtonSettings (+    buttonUrl,+    buttonValue,+    buttonStyle,+    buttonConfirm+  ),+  confirm,+  confirmAreYouSure,+  ConfirmSettings (+    confirmTitle,+    confirmText,+    confirmConfirm,+    confirmDeny,+    confirmStyle+  ), -    -- * Responding to Slack Interactive Messages-    SlackInteractiveResponse (..),-  )-where+  -- * Responding to Slack Interactive Messages+  SlackInteractiveResponse (..),+) where  import Data.Aeson.Text (encodeToLazyText) import Data.Text qualified as T@@ -81,6 +80,7 @@ import Web.Slack.Prelude import Web.Slack.Types +-- | Concatenate two 'SlackText's with a space separator. (<+>) :: SlackText -> SlackText -> SlackText x <+> y = x <> " " <> y @@ -97,7 +97,7 @@ ticks x = "`" <> x <> "`"  -- | Render a 'Slack' renderable value with ticks around it. Alias for @ticks . message@-monospaced :: Slack a => a -> SlackText+monospaced :: (Slack a) => a -> SlackText monospaced = ticks . message  codeBlock :: SlackText -> SlackText@@ -109,6 +109,7 @@ italic :: SlackText -> SlackText italic x = "_" <> x <> "_" +-- | Add a newline before a 'SlackText'. newline :: SlackText -> SlackText newline x = "\n" <> x @@ -161,22 +162,43 @@    in (SlackMessage truncatedBlocks, or isTruncateds)  truncateSlackBlock :: SlackBlock -> (SlackBlock, Bool)-truncateSlackBlock sb@(SlackBlockSection (SlackText texts) mAccessory) =-  let messageLength = sum $ map T.length texts+truncateSlackBlock sb@(SlackBlockSection SlackSection {..}) =+  let texts = maybe mempty unSlackTexts slackSectionText+      messageLength = sum $ map T.length texts       lengthLimit = 3000       truncationMessage = "\n...Rest of message truncated for slack\n"       truncationMessageLength = T.length truncationMessage       truncateTexts ts = take (lengthLimit - truncationMessageLength) (concat ts)+      truncatedSection =+        SlackBlockSection+          SlackSection+            { slackSectionText = Just $ SlackText [truncateTexts texts <> "\n...Rest of message truncated for slack\n"]+            , slackSectionAccessory+            , slackSectionBlockId+            , slackSectionFields+            }    in if messageLength > lengthLimit-        then (SlackBlockSection (SlackText [truncateTexts texts <> "\n...Rest of message truncated for slack\n"]) mAccessory, True)+        then (truncatedSection, True)         else (sb, False) -- possible to also truncate SlackContexts, but we never put long strings in there. truncateSlackBlock x = (x, False)  prefixFirstSlackBlockSection :: Text -> [SlackBlock] -> ([SlackBlock], Bool)-prefixFirstSlackBlockSection prefix (SlackBlockSection text mAccessory : sbs) = (SlackBlockSection (message prefix <> text) mAccessory : sbs, True)-prefixFirstSlackBlockSection prefix (sb : sbs) = let (prefixedSbs, match) = prefixFirstSlackBlockSection prefix sbs in (sb : prefixedSbs, match)-prefixFirstSlackBlockSection _ [] = ([], False)+prefixFirstSlackBlockSection prefix (SlackBlockSection SlackSection {..} : sbs) =+  let prefixedSection =+        SlackBlockSection+          SlackSection+            { slackSectionText = mappend (message prefix) <$> slackSectionText+            , slackSectionBlockId+            , slackSectionFields+            , slackSectionAccessory+            }+   in (prefixedSection : sbs, True)+prefixFirstSlackBlockSection prefix (sb : sbs) =+  let (prefixedSbs, match) = prefixFirstSlackBlockSection prefix sbs+   in (sb : prefixedSbs, match)+prefixFirstSlackBlockSection _ [] =+  ([], False)  prefixFirstSlackMessage :: Text -> [SlackMessage] -> [SlackMessage] prefixFirstSlackMessage prefix (sm : sms) =@@ -189,7 +211,7 @@  -- | Concatenate a list of 'SlackText' into a single block, and wrap it up as a full message slackMessage :: [SlackText] -> SlackMessage-slackMessage = SlackMessage . pure . (`SlackBlockSection` Nothing) . mconcat+slackMessage = SlackMessage . pure . SlackBlockSection . slackSectionWithText . mconcat  -- $interactive --
src/Web/Slack/Experimental/Blocks/Types.hs view
@@ -4,7 +4,7 @@ module Web.Slack.Experimental.Blocks.Types where  import Control.Monad (MonadFail (..))-import Data.Aeson (Object, Value (..), withArray)+import Data.Aeson (Object, Result (..), Value (..), fromJSON, withArray) import Data.Aeson.Types ((.!=)) import Data.StringVariants import Data.Vector qualified as V@@ -49,7 +49,7 @@  -- | Create a 'SlackPlainTextOnly'. Some API points can can take either markdown or plain text, -- but some can take only plain text. This enforces the latter.-plaintextonly :: Slack a => a -> SlackPlainTextOnly+plaintextonly :: (Slack a) => a -> SlackPlainTextOnly plaintextonly a = SlackPlainTextOnly $ message a  data SlackTextObject@@ -70,24 +70,24 @@       ]  -- | Create a plain text 'SlackTextObject' where the API allows either markdown or plain text.-plaintext :: Slack a => a -> SlackTextObject+plaintext :: (Slack a) => a -> SlackTextObject plaintext = SlackPlainText . message  -- | Create a markdown 'SlackTextObject' where the API allows either markdown or plain text.-mrkdwn :: Slack a => a -> SlackTextObject+mrkdwn :: (Slack a) => a -> SlackTextObject mrkdwn = SlackMarkdownText . message  instance FromJSON SlackTextObject where   parseJSON = withObject "SlackTextObject" $ \obj -> do     (slackTextType :: Text) <- obj .: "type"     case slackTextType of-      "text" -> do+      "plain_text" -> do         text <- obj .: "text"         pure . SlackPlainText . SlackText $ lines text       "mrkdwn" -> do         text <- obj .: "text"         pure . SlackMarkdownText . SlackText $ lines text-      _ -> fail "Unknown SlackTextObject type, must be one of ['text', 'mrkdwn']"+      _ -> fail "Unknown SlackTextObject type, must be one of ['plain_text', 'mrkdwn']"  instance Show SlackText where   show (SlackText arr) = show $ concat arr@@ -165,6 +165,13 @@   | SlackContentImage SlackImage   deriving stock (Eq) +slackContentToSlackText :: SlackContent -> Maybe SlackText+slackContentToSlackText c = case c of+  SlackContentText slackText ->+    Just slackText+  SlackContentImage _ ->+    Nothing+ instance Show SlackContent where   show (SlackContentText t) = show t   show (SlackContentImage i) = show i@@ -176,12 +183,12 @@       , "text" .= t       ]   toJSON (SlackContentImage (SlackImage mtitle altText url)) =-    object $-      [ "type" .= ("image" :: Text)-      , "image_url" .= url-      , "alt_text" .= altText-      ]-        <> maybe [] mkTitle mtitle+    object+      $ [ "type" .= ("image" :: Text)+        , "image_url" .= url+        , "alt_text" .= altText+        ]+      <> maybe [] mkTitle mtitle     where       mkTitle title =         [ "title"@@ -361,10 +368,34 @@  -- | Small helper function for constructing a section with a button accessory out of a button and text components sectionWithButtonAccessory :: SlackAction -> SlackText -> SlackBlock-sectionWithButtonAccessory btn txt = SlackBlockSection txt (Just $ SlackButtonAccessory btn)+sectionWithButtonAccessory btn txt =+  SlackBlockSection+    $ (slackSectionWithText txt)+      { slackSectionAccessory = Just $ SlackButtonAccessory btn+      } +-- | <https://api.slack.com/reference/block-kit/blocks#section>+data SlackSection = SlackSection+  { slackSectionText :: Maybe SlackText+  -- ^ May be absent if 'slackSectionFields' is present.+  , slackSectionBlockId :: Maybe SlackBlockId+  , slackSectionFields :: Maybe [SlackText]+  -- ^ Required if 'slackSectionText' is not provided.+  , slackSectionAccessory :: Maybe SlackAccessory+  }+  deriving stock (Eq, Show)++slackSectionWithText :: SlackText -> SlackSection+slackSectionWithText t =+  SlackSection+    { slackSectionText = Just t+    , slackSectionBlockId = Nothing+    , slackSectionFields = Nothing+    , slackSectionAccessory = Nothing+    }+ data SlackBlock-  = SlackBlockSection SlackText (Maybe SlackAccessory)+  = SlackBlockSection SlackSection   | SlackBlockImage SlackImage   | SlackBlockContext SlackContext   | SlackBlockDivider@@ -374,15 +405,13 @@   deriving stock (Eq)  instance Show SlackBlock where-  show (SlackBlockSection t Nothing) = show t-  show (SlackBlockSection t (Just mAccessory)) =-    show $ mconcat [show t, " [", show mAccessory, "]"]+  show (SlackBlockSection section) = show section   show (SlackBlockImage i) = show i   show (SlackBlockContext contents) = show contents   show SlackBlockDivider = "|"   show (SlackBlockActions mBlockId as) =-    show $-      mconcat+    show+      $ mconcat         [ "actions("         , show mBlockId         , ") = ["@@ -393,11 +422,13 @@   show (SlackBlockHeader p) = show p  instance ToJSON SlackBlock where-  toJSON (SlackBlockSection slackText mSectionAccessory) =+  toJSON (SlackBlockSection SlackSection {..}) =     objectOptional       [ "type" .=! ("section" :: Text)-      , "text" .=! SlackContentText slackText-      , "accessory" .=? mSectionAccessory+      , "text" .=? (SlackContentText <$> slackSectionText)+      , "block_id" .=? slackSectionBlockId+      , "fields" .=? (map SlackContentText <$> slackSectionFields)+      , "accessory" .=? slackSectionAccessory       ]   toJSON (SlackBlockImage i) = toJSON (SlackContentImage i)   toJSON (SlackBlockContext contents) =@@ -429,13 +460,24 @@     (slackBlockType :: Text) <- obj .: "type"     case slackBlockType of       "section" -> do-        (sectionContentObj :: Value) <- obj .: "text"-        SlackContentText sectionContentText <- parseJSON sectionContentObj-        mSectionAccessory <- obj .:? "accessory"-        pure $ SlackBlockSection sectionContentText mSectionAccessory+        slackSectionTextContent <- obj .:? "text"+        let slackSectionText = slackSectionTextContent >>= slackContentToSlackText+        slackSectionBlockId <- obj .:? "block_id"+        slackSectionFieldsContent <- obj .:? "fields"+        let slackSectionFields = slackSectionFieldsContent >>= traverse slackContentToSlackText+        (slackSectionAccessoryValue :: Maybe Value) <- obj .:? "accessory"+        -- The section accessory can be any block element but `SlackAcessory`+        -- only implements button.+        let slackSectionAccessory =+              slackSectionAccessoryValue >>= \v ->+                case fromJSON v of+                  Error _ ->+                    Nothing+                  Success slackAccessory ->+                    Just slackAccessory+        pure $ SlackBlockSection SlackSection {..}       "context" -> do-        (contextElementsObj :: Value) <- obj .: "elements"-        slackContent <- parseJSON contextElementsObj+        slackContent <- obj .: "elements"         pure $ SlackBlockContext slackContent       "image" -> do         SlackContentImage i <- parseJSON $ Object obj@@ -448,8 +490,9 @@       "rich_text" -> do         elements <- obj .: "elements"         mBlockId <- obj .:? "block_id"-        pure . SlackBlockRichText $-          RichText+        pure+          . SlackBlockRichText+          $ RichText             { blockId = mBlockId             , elements             }@@ -481,7 +524,7 @@   markdown :: SlackText -> a  instance Markdown SlackMessage where-  markdown t = SlackMessage [SlackBlockSection t Nothing]+  markdown t = SlackMessage [SlackBlockSection (slackSectionWithText t)]  instance Markdown SlackContext where   markdown t = SlackContext [SlackContentText t]@@ -502,11 +545,11 @@ textToContext = context . markdown . message  -- | Generates interactive components such as buttons.-actions :: ToSlackActionList as => as -> SlackMessage+actions :: (ToSlackActionList as) => as -> SlackMessage actions as = SlackMessage [SlackBlockActions Nothing $ toSlackActionList as]  -- | Generates interactive components such as buttons with a 'SlackBlockId'.-actionsWithBlockId :: ToSlackActionList as => SlackBlockId -> as -> SlackMessage+actionsWithBlockId :: (ToSlackActionList as) => SlackBlockId -> as -> SlackMessage actionsWithBlockId slackBlockId as = SlackMessage [SlackBlockActions (Just slackBlockId) $ toSlackActionList as]  -- | Settings for [button elements](https://api.slack.com/reference/block-kit/block-elements#button).@@ -536,8 +579,8 @@ -- | Button builder. button :: SlackActionId -> SlackButtonText -> ButtonSettings -> SlackAction button actionId buttonText ButtonSettings {..} =-  SlackAction actionId $-    SlackButton+  SlackAction actionId+    $ SlackButton       { slackButtonText = buttonText       , slackButtonUrl = unOptionalSetting buttonUrl       , slackButtonValue = unOptionalSetting buttonValue@@ -545,6 +588,13 @@       , slackButtonConfirm = unOptionalSetting buttonConfirm       } +-- | A divider block.+-- https://api.slack.com/reference/block-kit/blocks#divider+--+-- @since 1.6.2.0+divider :: SlackMessage+divider = SlackMessage [SlackBlockDivider]+ -- | Settings for [confirmation dialog objects](https://api.slack.com/reference/block-kit/composition-objects#confirm). data ConfirmSettings = ConfirmSettings   { confirmTitle :: Text@@ -603,7 +653,7 @@   , slackConfirmDeny :: SlackPlainTextOnly -- max length 30   , slackConfirmStyle :: Maybe SlackStyle   }-  deriving stock (Eq)+  deriving stock (Eq, Show)  instance ToJSON SlackConfirmObject where   toJSON SlackConfirmObject {..} =
src/Web/Slack/Experimental/Events/Types.hs view
@@ -1,4 +1,7 @@+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-}  -- FIXME(jadel): Use NoFieldSelectors when we drop everything before 9.2.@@ -8,6 +11,8 @@  import Data.Aeson import Data.Aeson.TH+import Data.Aeson.Types (Parser)+import Data.Scientific qualified as Sci import Data.Time.Clock.System (SystemTime) import Web.Slack.AesonUtils import Web.Slack.Experimental.Blocks (SlackBlock)@@ -22,6 +27,123 @@  $(deriveJSON snakeCaseOptions ''ChannelType) +-- | <https://api.slack.com/events/message/message_attachment>+-- Ported from https://github.com/slackapi/node-slack-sdk/blob/fc87d51/packages/types/src/message-attachments.ts+--+-- @since 2.0.0.3+data AttachmentField = AttachmentField+  { title :: Text+  , value :: Text+  , short :: Maybe Bool+  }+  deriving stock (Show)++$(deriveFromJSON snakeCaseOptions ''AttachmentField)++-- | @since 2.0.0.3+data AttachmentMessageBlockMessage = AttachmentMessageBlockMessage+  { blocks :: [SlackBlock]+  }+  deriving stock (Show)++$(deriveFromJSON snakeCaseOptions ''AttachmentMessageBlockMessage)++-- | @since 2.0.0.3+data AttachmentMessageBlock = AttachmentMessageBlock+  { team :: TeamId+  , channel :: ConversationId+  , ts :: Text+  , message :: AttachmentMessageBlockMessage+  }+  deriving stock (Show)++$(deriveFromJSON snakeCaseOptions ''AttachmentMessageBlock)++-- | <https://api.slack.com/events/message/message_attachment>+-- Ported from https://github.com/slackapi/node-slack-sdk/blob/fc87d51/packages/types/src/message-attachments.ts+--+-- @since 2.0.0.3+data DecodedMessageAttachment = DecodedMessageAttachment+  { fallback :: Maybe Text+  , color :: Maybe Text+  , pretext :: Maybe Text+  , authorName :: Maybe Text+  , authorLink :: Maybe Text+  , authorIcon :: Maybe Text+  , title :: Maybe Text+  , titleLink :: Maybe Text+  , text :: Maybe Text+  , fields :: Maybe [AttachmentField]+  , imageUrl :: Maybe Text+  , thumbUrl :: Maybe Text+  , footer :: Maybe Text+  , footerIcon :: Maybe Text+  , ts :: Maybe Text+  , -- the following are undocumented+    isMsgUnfurl :: Maybe Bool+  , messageBlocks :: Maybe [AttachmentMessageBlock]+  -- ^ unfurled message blocks+  , authorId :: Maybe UserId+  , channelId :: Maybe ConversationId+  , channelTeam :: Maybe TeamId+  , isAppUnfurl :: Maybe Bool+  , appUnfurlUrl :: Maybe Text+  , fromUrl :: Maybe Text+  }+  deriving stock (Show)++instance FromJSON DecodedMessageAttachment where+  parseJSON = withObject "DecodedMessageAttachment" $ \v -> do+    fallback <- v .:? "fallback"+    color <- v .:? "color"+    pretext <- v .:? "pretext"+    authorName <- v .:? "author_name"+    authorLink <- v .:? "author_link"+    authorIcon <- v .:? "author_icon"+    title <- v .:? "title"+    titleLink <- v .:? "title_link"+    text <- v .:? "text"+    fields <- v .:? "fields"+    imageUrl <- v .:? "image_url"+    thumbUrl <- v .:? "thumb_url"+    footer <- v .:? "footer"+    footerIcon <- v .:? "footer_icon"+    ts <- v .:? "ts" >>= maybe (return Nothing) parseTs+    isMsgUnfurl <- v .:? "is_msg_unfurl"+    messageBlocks <- v .:? "message_blocks"+    authorId <- v .:? "author_id"+    channelId <- v .:? "channel_id"+    channelTeam <- v .:? "channel_team"+    isAppUnfurl <- v .:? "is_app_unfurl"+    appUnfurlUrl <- v .:? "app_unfurl_url"+    fromUrl <- v .:? "from_url"+    pure DecodedMessageAttachment {..}+    where+      parseTs :: Value -> Parser (Maybe Text)+      parseTs (String s) = pure $ Just s+      parseTs (Number n) =+        let s = Sci.formatScientific Sci.Fixed Nothing n+            formatted = if '.' `elem` s then s else s ++ ".000000"+         in pure $ Just (pack formatted)+      parseTs _ = pure Nothing++data MessageAttachment = MessageAttachment+  { decoded :: Maybe DecodedMessageAttachment+  -- ^ If the attachment can be decoded, this will be populated+  , raw :: Value+  -- ^ Slack does not document the attachment schema/spec very well and we can't+  -- decode many attachments. In these cases clients can work with the raw JSON.+  }+  deriving stock (Show)++instance FromJSON MessageAttachment where+  parseJSON = withObject "MessageAttachment" $ \v -> do+    let ov = Object v+    -- Attempt to parse the entire object as DecodedMessageAttachment+    decodedContent <- optional $ parseJSON ov+    -- Return the structured data with raw JSON preserved+    pure MessageAttachment {decoded = decodedContent, raw = ov}+ -- | <https://api.slack.com/events/message> -- and -- <https://api.slack.com/events/message/file_share>@@ -48,11 +170,40 @@   --   See @botMessage.json@ golden parser test.   , botId :: Maybe Text   -- ^ Present if it's sent by a bot user+  , attachments :: Maybe [MessageAttachment]+  -- ^ @since 2.0.0.3+  -- Present if the message has attachments   }   deriving stock (Show)  $(deriveFromJSON snakeCaseOptions ''MessageEvent) +-- | <https://api.slack.com/events/message/bot_message>+-- This is similar to a MessageEvent but sent by a bot, for example+-- messages that Reacji Channeler sends.+--+-- @since 2.0.0.2+data BotMessageEvent = BotMessageEvent+  { blocks :: Maybe [SlackBlock]+  , channel :: ConversationId+  , text :: Text+  , channelType :: ChannelType+  , files :: Maybe [FileObject]+  , ts :: Text+  , threadTs :: Maybe Text+  -- ^ Present if the message is in a thread+  , appId :: Maybe Text+  -- ^ Some (or all) bots also have an App ID+  , botId :: Text+  -- ^ Always present for bot_message subtype+  , attachments :: Maybe [MessageAttachment]+  -- ^ @since 2.0.0.3+  -- Present if the message has attachments+  }+  deriving stock (Show)++$(deriveFromJSON snakeCaseOptions ''BotMessageEvent)+ -- | <https://api.slack.com/events/message/message_changed> -- -- FIXME(jadel): implement. This is mega cursed! in the normal message event@@ -124,6 +275,7 @@  data Event   = EventMessage MessageEvent+  | EventBotMessage BotMessageEvent   | EventMessageChanged   | -- | Weird message event of subtype channel_join. Sent "sometimes", according     -- to a random Slack blog post from 2017:@@ -142,6 +294,7 @@     subtype :: Maybe Text <- obj .:? "subtype"     case (tag, subtype) of       ("message", Nothing) -> EventMessage <$> parseJSON @MessageEvent (Object obj)+      ("message", Just "bot_message") -> EventBotMessage <$> parseJSON @BotMessageEvent (Object obj)       ("message", Just "message_changed") -> pure EventMessageChanged       ("message", Just "channel_join") -> pure EventChannelJoinMessage       -- n.b. these are unified since it is *identical* to a Message event with
src/Web/Slack/Experimental/RequestVerification.hs view
@@ -1,12 +1,11 @@-module Web.Slack.Experimental.RequestVerification-  ( SlackSigningSecret (..),-    SlackSignature (..),-    SlackRequestTimestamp (..),-    SlackVerificationFailed (..),-    validateRequest,-    validateRequest',-  )-where+module Web.Slack.Experimental.RequestVerification (+  SlackSigningSecret (..),+  SlackSignature (..),+  SlackRequestTimestamp (..),+  SlackVerificationFailed (..),+  validateRequest,+  validateRequest',+) where  import Crypto.Hash (SHA256, digestFromByteString) import Crypto.MAC.HMAC@@ -70,7 +69,7 @@  -- | Pure version of 'validateRequest'. Probably only useful for tests. validateRequest' ::-  FromJSON a =>+  (FromJSON a) =>   NominalDiffTime ->   SlackSigningSecret ->   SlackSignature ->@@ -81,21 +80,23 @@   let fiveMinutes = 5 * 60   -- timestamp must be an Int for proper basestring construction below   timestamp <--    maybeToRight (VerificationMalformedTimestamp timestampString) $-      fst <$> readInt timestampString+    maybeToRight (VerificationMalformedTimestamp timestampString)+      $ fst+      <$> readInt timestampString   if abs (now - fromIntegral timestamp) > fiveMinutes     then Left $ VerificationTimestampOutOfRange timestamp     else Right ()   sigHeaderStripped <--    maybeToRight (VerificationUnknownSignatureVersion sigHeader) $-      stripPrefix "v0=" sigHeader+    maybeToRight (VerificationUnknownSignatureVersion sigHeader)+      $ stripPrefix "v0=" sigHeader   sigDecoded <--    mapLeft VerificationMalformedSignature $-      B16.decode sigHeaderStripped+    mapLeft VerificationMalformedSignature+      $ B16.decode sigHeaderStripped   sig :: HMAC SHA256 <--    maybeToRight (VerificationUndecodableSignature sigDecoded) $-      HMAC <$> digestFromByteString sigDecoded+    maybeToRight (VerificationUndecodableSignature sigDecoded)+      $ HMAC+      <$> digestFromByteString sigDecoded   let basestring = encodeUtf8 ("v0:" <> tshow timestamp <> ":") <> body-  when (hmac secret basestring /= sig) $-    Left VerificationSignatureMismatch+  when (hmac secret basestring /= sig)+    $ Left VerificationSignatureMismatch   mapLeft (VerificationCannotParse . pack) $ eitherDecodeStrict body
src/Web/Slack/Internal.hs view
@@ -27,7 +27,7 @@ -- Internal type! newtype ResponseJSON a = ResponseJSON (Either ResponseSlackError a) -instance FromJSON a => FromJSON (ResponseJSON a) where+instance (FromJSON a) => FromJSON (ResponseJSON a) where   parseJSON = withObject "Response" $ \o -> do     ok <- o .: "ok"     ResponseJSON
src/Web/Slack/MessageParser.hs view
@@ -4,12 +4,11 @@ {-# LANGUAGE RecordWildCards #-}  -- | See https://api.slack.com/docs/message-formatting-module Web.Slack.MessageParser-  ( messageToHtml,-    HtmlRenderers (..),-    defaultHtmlRenderers,-  )-where+module Web.Slack.MessageParser (+  messageToHtml,+  HtmlRenderers (..),+  defaultHtmlRenderers,+) where  -- FIXME: Web.Slack.Prelude 
src/Web/Slack/Pager.hs view
@@ -1,11 +1,10 @@-module Web.Slack.Pager-  ( Response,-    LoadPage,-    loadingPage,-    fetchAllBy,-    module Web.Slack.Pager.Types,-  )-where+module Web.Slack.Pager (+  Response,+  LoadPage,+  loadingPage,+  fetchAllBy,+  module Web.Slack.Pager.Types,+) where  import Web.Slack.Common qualified as Common import Web.Slack.Conversation qualified as Conversation
src/Web/Slack/Prelude.hs view
@@ -1,14 +1,13 @@-module Web.Slack.Prelude-  ( module ClassyPrelude,-    module Data.Aeson,-    module Data.Aeson.TH,-    cs,-    ToHttpApiData,-    NonEmpty (..),-    Default (..),-    Proxy (..),-  )-where+module Web.Slack.Prelude (+  module ClassyPrelude,+  module Data.Aeson,+  module Data.Aeson.TH,+  cs,+  ToHttpApiData,+  NonEmpty (..),+  Default (..),+  Proxy (..),+) where  import ClassyPrelude hiding (link) import Data.Aeson (FromJSON (..), ToJSON (..), object, withObject, (.:), (.:?), (.=))
src/Web/Slack/Types.hs view
@@ -5,18 +5,17 @@ -- | -- Module: Web.Slack.Types -- Description:-module Web.Slack.Types-  ( Color (..),-    UserId (..),-    ConversationId (..),-    TeamId (..),-    Cursor (..),-    SlackTimestamp (..),-    mkSlackTimestamp,-    timestampFromText,-    SlackMessageText (..),-  )-where+module Web.Slack.Types (+  Color (..),+  UserId (..),+  ConversationId (..),+  TeamId (..),+  Cursor (..),+  SlackTimestamp (..),+  mkSlackTimestamp,+  timestampFromText,+  SlackMessageText (..),+) where  import Control.Monad (MonadFail (..)) import Data.Aeson@@ -87,9 +86,9 @@  instance FromJSON SlackTimestamp where   parseJSON =-    withText "Slack ts" $-      either (fail . ("Invalid Slack ts: " ++)) pure-        . timestampFromText+    withText "Slack ts"+      $ either (fail . ("Invalid Slack ts: " ++)) pure+      . timestampFromText  instance ToJSON SlackTimestamp where   toJSON = String . slackTimestampTs
src/Web/Slack/User.hs view
@@ -4,15 +4,14 @@ {-# LANGUAGE TemplateHaskell #-}  -- | @users.*@ methods in the Slack API-module Web.Slack.User-  ( Profile (..),-    User (..),-    ListReq (..),-    ListRsp (..),-    Email (..),-    UserRsp (..),-  )-where+module Web.Slack.User (+  Profile (..),+  User (..),+  ListReq (..),+  ListRsp (..),+  Email (..),+  UserRsp (..),+) where  import Data.Time.Clock.POSIX import Web.FormUrlEncoded
src/Web/Slack/UsersConversations.hs view
@@ -3,13 +3,12 @@  -- | Client for Slack's @users.conversations@ endpoint. -- <https://api.slack.com/methods/users.conversations>-module Web.Slack.UsersConversations-  ( UsersConversationsRequest (..),-    UsersConversationsResponse (..),-    usersConversations,-    usersConversationsAll,-  )-where+module Web.Slack.UsersConversations (+  UsersConversationsRequest (..),+  UsersConversationsResponse (..),+  usersConversations,+  usersConversationsAll,+) where  import Servant.API import Servant.Client hiding (Response)
src/Web/Slack/Util.hs view
@@ -5,12 +5,11 @@ -- | -- Module: Web.Slack.Util -- Description:-module Web.Slack.Util-  ( formOpts,-    jsonOpts,-    toQueryParamIfJust,-  )-where+module Web.Slack.Util (+  formOpts,+  jsonOpts,+  toQueryParamIfJust,+) where  -- FIXME: Web.Slack.Prelude @@ -59,6 +58,6 @@ addUnderscores =   camelTo2 '_' -toQueryParamIfJust :: ToHttpApiData a => Text -> Maybe a -> Form+toQueryParamIfJust :: (ToHttpApiData a) => Text -> Maybe a -> Form toQueryParamIfJust key =   fromList . maybeToList . fmap (\justVal -> (key, toQueryParam justVal))
tests/JSONGolden.hs view
@@ -20,7 +20,7 @@ filename :: Text -> Text -> FilePath filename tycon name = $(LitE . StringL <$> makeRelativeToProject "tests/golden") </> unpack tycon </> unpack name -typeName :: forall a. Typeable a => Text+typeName :: forall a. (Typeable a) => Text typeName = pack . tyConName . typeRepTyCon $ typeRep @a  goldenTestDecode :: forall a. (FromJSON a, Show a, Typeable a) => Text -> LByteString -> Golden Text
tests/TestImport.hs view
@@ -1,15 +1,14 @@-module TestImport-  ( fromJust,-    module Control.Monad.Fail,-    module Test.Hspec,-    module Test.Hspec.QuickCheck,-    module ClassyPrelude,-    module Data.Aeson,-    module Data.Aeson.TH,-    cs,-    module Test.QuickCheck,-  )-where+module TestImport (+  fromJust,+  module Control.Monad.Fail,+  module Test.Hspec,+  module Test.Hspec.QuickCheck,+  module ClassyPrelude,+  module Data.Aeson,+  module Data.Aeson.TH,+  cs,+  module Test.QuickCheck,+) where  import ClassyPrelude hiding (link) import Control.Monad.Fail
tests/TestImport/Aeson.hs view
@@ -9,6 +9,8 @@       parsed = A.parse (parseJSON @a) encoded       roundTwo = fmap (toJSON @a) parsed    in -- The encoding is the same-      A.Success encoded == roundTwo+      A.Success encoded+        == roundTwo         -- AND the object itself is the same-        && A.Success a == parsed+        && A.Success a+        == parsed
tests/Web/Slack/ConversationSpec.hs view
@@ -3,10 +3,9 @@ {-# LANGUAGE StandaloneDeriving #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -module Web.Slack.ConversationSpec-  ( spec,-  )-where+module Web.Slack.ConversationSpec (+  spec,+) where  -- aeson import Data.Aeson
+ tests/Web/Slack/Experimental/Blocks/TypesSpec.hs view
@@ -0,0 +1,162 @@+module Web.Slack.Experimental.Blocks.TypesSpec where++import Data.Aeson qualified as Aeson+import Data.StringVariants.NonEmptyText.Internal (pattern NonEmptyText)+import Refined.Unsafe (reallyUnsafeRefine)+import TestImport+import Web.Slack.Experimental.Blocks.Types++jsonRoundtrips :: (Show a, Eq a, Aeson.ToJSON a, Aeson.FromJSON a) => a -> Spec+jsonRoundtrips a = do+  it "can decode its own json encoding" do+    (Aeson.fromJSON . Aeson.toJSON) a `shouldBe` Aeson.Success a++spec :: Spec+spec = do+  let+    aSlackAccessory = SlackButtonAccessory aSlackAction+    aSlackAction = SlackAction+      do SlackActionId $ NonEmptyText "action-id"+      do aSlackButton+    aSlackActionList =+      SlackActionList+        . reallyUnsafeRefine+        $ [ aSlackAction+          , SlackAction+              do SlackActionId $ NonEmptyText "another-action-id"+              do+                SlackButton+                  do SlackButtonText $ NonEmptyText "another-button-text"+                  do Nothing+                  do Nothing+                  do Nothing+                  do Nothing+          ]+    aSlackBlockSection = SlackBlockSection aSlackSection+    aSlackBlockImage = SlackBlockImage aSlackImage+    aSlackBlockContext = SlackBlockContext aSlackContext+    aSlackBlockActions = SlackBlockActions+      do Just $ NonEmptyText "block-actions"+      do aSlackActionList+    aSlackBlockHeader = SlackBlockHeader $ SlackPlainTextOnly "block-header"+    aSlackButton =+      SlackButton+        { slackButtonText = SlackButtonText (NonEmptyText "button-text")+        , slackButtonUrl = Just (NonEmptyText "button-url")+        , slackButtonValue = Just (NonEmptyText "button-value")+        , slackButtonStyle = Just SlackStylePrimary+        , slackButtonConfirm = Just aSlackConfirmObject+        }+    aSlackConfirmObject =+      SlackConfirmObject+        { slackConfirmTitle = SlackPlainTextOnly "button-confirm-title"+        , slackConfirmText = SlackPlainText "button-confirm-text"+        , slackConfirmConfirm = SlackPlainTextOnly "button-confirm-confirm"+        , slackConfirmDeny = SlackPlainTextOnly "button-confirm-deny"+        , slackConfirmStyle = Just SlackStyleDanger+        }+    aSlackContentText = SlackContentText "content-text"+    aSlackContentImage = SlackContentImage aSlackImage+    aSlackContext = SlackContext [aSlackContentText, aSlackContentImage]+    aSlackImage =+      SlackImage+        { slackImageTitle = Just "image-title"+        , slackImageAltText = "image-alt-text"+        , slackImageUrl = "image-url"+        }+    aSlackMessage =+      SlackMessage+        [ aSlackBlockSection+        , aSlackBlockImage+        , aSlackBlockContext+        , aSlackBlockActions+        , aSlackBlockHeader+        -- not tested: SlackBlockRichText+        ]+    aSlackPlainTextOnly = SlackPlainTextOnly "plain-text-only"+    aSlackPlainText = SlackPlainText "plain-text"+    aSlackMarkdownText = SlackMarkdownText "markdown-text"+    aSlackSection =+      SlackSection+        { slackSectionText = Just "section-text"+        , slackSectionBlockId = Just (NonEmptyText "section-block-id")+        , slackSectionFields = Just ["field-0", "field-1", "field-2"]+        , slackSectionAccessory = Just aSlackAccessory+        }++  describe "SlackAccessory" do+    jsonRoundtrips aSlackAccessory++  describe "SlackAction" do+    jsonRoundtrips aSlackAction++  describe "SlackBlock" do+    describe "SlackBlockSection" do+      jsonRoundtrips aSlackBlockSection++    describe "SlackBlockImage" do+      jsonRoundtrips aSlackBlockImage++    describe "SlackBlockContext" do+      jsonRoundtrips aSlackBlockContext++    describe "SlackBlockDivider" do+      jsonRoundtrips SlackBlockDivider++    -- SlackBlock's ToJSON instance is lossy; SlackBlockRichText values get+    -- encoded as '{}'+    --+    -- describe "SlackBlockRichText" do+    --   jsonRoundtrips aSlackBlockRichText++    describe "SlackBlockActions" do+      jsonRoundtrips aSlackBlockActions++    describe "SlackBlockHeader" do+      jsonRoundtrips aSlackBlockHeader++  describe "SlackConfirmObject" do+    jsonRoundtrips aSlackConfirmObject++  describe "SlackContent" do+    describe "SlackContentText" do+      jsonRoundtrips aSlackContentText++    describe "SlackContentImage" do+      jsonRoundtrips aSlackContentImage++  describe "SlackContext" do+    jsonRoundtrips aSlackContext++  describe "SlackMessage" do+    jsonRoundtrips aSlackMessage++  describe "SlackPlainTextOnly" do+    jsonRoundtrips aSlackPlainTextOnly++  describe "SlackTextObject" do+    describe "SlackPlainText" do+      jsonRoundtrips aSlackPlainText++    describe "SlackMarkdownText" do+      jsonRoundtrips aSlackMarkdownText++-- Untestable for roundtripping:+--+--  FromJSON only+--    - RichItem+--    - RichStyle+--    - RichText+--    - RichTextSectionItem+--    - SlackActionComponent+--    - SlackActionResponse+--    - SlackInteractivePayload+--    - SlackInteractiveResponseResponse+--+--  ToJSON only+--    - SlackInteractiveResponse+--    - SlackText+--+--  Note also that SlackBlock's encoding is lossy, encoding+--  SlackBlockRichText as '{}', so that case is not tested+--  for round-tripping
tests/Web/Slack/Experimental/BlocksSpec.hs view
@@ -14,13 +14,34 @@ headerBlock = tell . pure . SlackBlockHeader . SlackPlainTextOnly  sectionBlock :: SlackText -> WriterT [SlackBlock] Identity ()-sectionBlock text = tell . pure $ SlackBlockSection text Nothing+sectionBlock = tell . pure . SlackBlockSection . slackSectionWithText +sectionBlockWithFields :: SlackText -> [SlackText] -> WriterT [SlackBlock] Identity ()+sectionBlockWithFields text fields =+  tell+    $ pure+    $ SlackBlockSection+    $ SlackSection+      { slackSectionText = Just text+      , slackSectionBlockId = Nothing+      , slackSectionFields = Just fields+      , slackSectionAccessory = Nothing+      }+ sectionBlockWithAccessory ::   SlackText ->   SlackAction ->   WriterT [SlackBlock] Identity ()-sectionBlockWithAccessory a b = tell . pure $ SlackBlockSection a (Just . SlackButtonAccessory $ b)+sectionBlockWithAccessory t b =+  tell+    $ pure+    $ SlackBlockSection+      SlackSection+        { slackSectionText = Just t+        , slackSectionBlockId = Nothing+        , slackSectionFields = Nothing+        , slackSectionAccessory = Just $ SlackButtonAccessory b+        }  dividerBlock :: WriterT [SlackBlock] Identity () dividerBlock = tell . pure $ SlackBlockDivider@@ -45,8 +66,9 @@  slackShowBlockFormat :: BlockKitBuilderMessage slackShowBlockFormat =-  BlockKitBuilderMessage $-    execWriter $ do+  BlockKitBuilderMessage+    $ execWriter+    $ do       headerBlock ("Blah: " <> list ["a", "b", "c"])       dividerBlock       sectionBlock (bold $ list ["blah", "blah2", "blah3"])@@ -63,10 +85,17 @@       sectionBlockWithAccessory         (bold . textMessage $ "blah")         (button slackActionDoNothing ":bank: Look at it!" buttonSettings {buttonUrl = google})+      sectionBlockWithFields+        (message @Text "Section with text and fields")+        [ (bold $ message @Text "Field 1") <> (newline $ message @Text "Foo")+        , (bold $ message @Text "Field 2") <> (newline $ message @Text "Bar")+        , (bold $ message @Text "Field 3") <> (newline $ message @Text "Baz")+        , (bold $ message @Text "Field 4") <> (newline $ message @Text "Qux")+        ]       contextBlock [SlackContentText ":key: Context!"]       dividerBlock-      actionsBlock Nothing $-        toSlackActionList+      actionsBlock Nothing+        $ toSlackActionList           ( button slackActionDoNothing ":mag: View" buttonSettings {buttonUrl = google}           , button slackActionDoNothing2 ":office: View" buttonSettings {buttonUrl = google}           , button slackActionDoNothing3 ":bank: View" buttonSettings {buttonUrl = google}
tests/Web/Slack/Experimental/Events/TypesSpec.hs view
@@ -23,6 +23,14 @@         , "slackbotIm"         , "channel_left"         , "share_without_message"+        , "share_with_message"+        , "circleci"         , -- https://slack.com/help/articles/206819278-Send-emails-to-Slack           "email_message"+        , "message_subtype_bot_message"+        , "forwarded_message"+        , "github_notification"+        , "github_notification_ts_string"+        , "github_with_link"+        , "non_spec_attachment"         ]
tests/Web/Slack/MessageParserSpec.hs view
@@ -20,36 +20,46 @@ spec :: Spec spec =   describe "message contents parsing" $ do-    it "handles the trivial case well" $-      msgToHtml "hello" `shouldBe` "hello"-    it "converts a simple message to HTML correctly" $-      msgToHtml "_hello_ *world* <http://www.google.com|google> `code` ```longer\ncode```"-        `shouldBe` "<i>hello</i> <b>world</b> <a href='http://www.google.com'>google</a> <code>code</code> <pre>longer\ncode</pre>"-    it "degrades properly to return the input message if it's incorrect" $-      msgToHtml "link not closed <bad"-        `shouldBe` "link not closed <bad"-    it "creates italics sections only at word boundaries" $-      msgToHtml "false_positive" `shouldBe` "false_positive"-    it "handles bold, strikethrough & italics simultaneously" $-      msgToHtml "*_~both~_*" `shouldBe` "<b><i><strike>both</strike></i></b>"-    it "aborts nicely on interspersed bold & italics" $-      msgToHtml "inter *sper_ *sed_" `shouldBe` "inter *sper_ *sed_"-    it "parses blockquotes properly" $-      msgToHtml "look at this:\n&gt; test *wow*" `shouldBe` "look at this:<br/><blockquote>test <b>wow</b></blockquote>"-    it "parses code blocks properly" $-      msgToHtml "look at this:\n```test *wow*```" `shouldBe` "look at this:<br/><pre>test *wow*</pre>"-    it "handles non-italics underscores in text well" $+    it "handles the trivial case well"+      $ msgToHtml "hello"+      `shouldBe` "hello"+    it "converts a simple message to HTML correctly"+      $ msgToHtml "_hello_ *world* <http://www.google.com|google> `code` ```longer\ncode```"+      `shouldBe` "<i>hello</i> <b>world</b> <a href='http://www.google.com'>google</a> <code>code</code> <pre>longer\ncode</pre>"+    it "degrades properly to return the input message if it's incorrect"+      $ msgToHtml "link not closed <bad"+      `shouldBe` "link not closed <bad"+    it "creates italics sections only at word boundaries"+      $ msgToHtml "false_positive"+      `shouldBe` "false_positive"+    it "handles bold, strikethrough & italics simultaneously"+      $ msgToHtml "*_~both~_*"+      `shouldBe` "<b><i><strike>both</strike></i></b>"+    it "aborts nicely on interspersed bold & italics"+      $ msgToHtml "inter *sper_ *sed_"+      `shouldBe` "inter *sper_ *sed_"+    it "parses blockquotes properly"+      $ msgToHtml "look at this:\n&gt; test *wow*"+      `shouldBe` "look at this:<br/><blockquote>test <b>wow</b></blockquote>"+    it "parses code blocks properly"+      $ msgToHtml "look at this:\n```test *wow*```"+      `shouldBe` "look at this:<br/><pre>test *wow*</pre>"+    it "handles non-italics underscores in text well"+      $       -- need to put other HTML symbols, otherwise if the parsing fails       -- i won't find out since we default to returning the input on       -- parsing failure-      msgToHtml "a:\n&gt;b.\n:slightly_smiling_face:" `shouldBe` "a:<br/><blockquote>b.</blockquote>:>slightly_smiling_face<:"-    it "properly parses multiline blockquotes" $-      msgToHtml "&gt; first row\n&gt; second row\nthird row\n&gt; fourth row"-        `shouldBe` "<blockquote>first row<br/>second row</blockquote>third row<br/><blockquote>fourth row</blockquote>"-    it "converts usernames" $-      msgToHtml "<@USER1> should be converted, <@USER1|default> stay default"-        `shouldBe` "@user_one should be converted, @default stay default"-    it "converts carriage returns" $-      msgToHtml "a\nb" `shouldBe` "a<br/>b"-    it "handles full stops as punctuation" $-      msgToHtml "*b*." `shouldBe` "<b>b</b>."+      msgToHtml "a:\n&gt;b.\n:slightly_smiling_face:"+      `shouldBe` "a:<br/><blockquote>b.</blockquote>:>slightly_smiling_face<:"+    it "properly parses multiline blockquotes"+      $ msgToHtml "&gt; first row\n&gt; second row\nthird row\n&gt; fourth row"+      `shouldBe` "<blockquote>first row<br/>second row</blockquote>third row<br/><blockquote>fourth row</blockquote>"+    it "converts usernames"+      $ msgToHtml "<@USER1> should be converted, <@USER1|default> stay default"+      `shouldBe` "@user_one should be converted, @default stay default"+    it "converts carriage returns"+      $ msgToHtml "a\nb"+      `shouldBe` "a<br/>b"+    it "handles full stops as punctuation"+      $ msgToHtml "*b*."+      `shouldBe` "<b>b</b>."
tests/Web/Slack/PagerSpec.hs view
@@ -5,20 +5,20 @@ import Data.Time.Clock (addUTCTime, nominalDay) import Test.Pull.Fake.IO (FakeStream, newFakeStream, pull) import TestImport-import Web.Slack.Common-  ( Message (..),-    MessageType (..),-    SlackMessageText (..),-    mkSlackTimestamp,-  )-import Web.Slack.Conversation-  ( ConversationId (ConversationId),-    HistoryReq (..),-    HistoryRsp (..),-    RepliesReq (..),-    mkHistoryReq,-    mkRepliesReq,-  )+import Web.Slack.Common (+  Message (..),+  MessageType (..),+  SlackMessageText (..),+  mkSlackTimestamp,+ )+import Web.Slack.Conversation (+  ConversationId (ConversationId),+  HistoryReq (..),+  HistoryRsp (..),+  RepliesReq (..),+  mkHistoryReq,+  mkRepliesReq,+ ) import Web.Slack.Pager import Web.Slack.Types (UserId (..)) @@ -38,16 +38,17 @@               -- The last page's cursor can be either an empty string, null, or non-exisitent in the object.               (pageN, cursor) <- zip [1 .. 3] ["cursor1=", "cursor2=", ""]               let pageNT = Text.pack (show pageN)-              pure . Right $-                HistoryRsp+              pure+                . Right+                $ HistoryRsp                   { historyRspMessages = do                       messageN <- [1 .. messagesPerPage]                       let messagesPerPageNDT = fromIntegral messagesPerPage                           messageNDT = fromIntegral messageN                           messageNT = Text.pack (show messageN)                           createdBefore = negate $ nominalDay * ((pageN - 1) * messagesPerPageNDT + messageNDT)-                      pure $-                        Message+                      pure+                        $ Message                           MessageTypeMessage                           (Just . UserId $ "U" <> pageNT <> messageNT)                           (SlackMessageText $ "message " <> pageNT <> "-" <> messageNT)@@ -57,8 +58,9 @@         responsesToReturn <- newFakeStream allResponses         return (now, oldest, messagesPerPage, allResponses, responsesToReturn) -  describe "conversationsHistoryAllBy" $-    it "collect all results by sending requests" $ do+  describe "conversationsHistoryAllBy"+    $ it "collect all results by sending requests"+    $ do       (now, oldest, messagesPerPage, allResponses, responsesToReturn) <- prepare       let initialRequest =             (mkHistoryReq (ConversationId "C01234567"))@@ -72,8 +74,9 @@       expected <- fmap (map historyRspMessages) . either throwIO return $ sequenceA allResponses       actual `shouldReturn` expected -  describe "repliesFetchAllBy" $-    it "collect all results by sending requests" $ do+  describe "repliesFetchAllBy"+    $ it "collect all results by sending requests"+    $ do       (now, oldest, messagesPerPage, allResponses, responsesToReturn) <- prepare       let initialRequest =             (mkRepliesReq (ConversationId "C98765432") oldest)@@ -88,7 +91,7 @@       actual `shouldReturn` expected  -- | Runs the given action repeatedly until it returns an empty list.-unfoldPageM :: Monad m => m [a] -> m [[a]]+unfoldPageM :: (Monad m) => m [a] -> m [[a]] unfoldPageM act = reverse <$> go []   where     go accum = do
tests/golden/BlockKitBuilderMessage/simple_blocks.golden.json view
@@ -90,6 +90,31 @@             "type": "section"         },         {+            "fields": [+                {+                    "text": "*Field 1*\nFoo",+                    "type": "mrkdwn"+                },+                {+                    "text": "*Field 2*\nBar",+                    "type": "mrkdwn"+                },+                {+                    "text": "*Field 3*\nBaz",+                    "type": "mrkdwn"+                },+                {+                    "text": "*Field 4*\nQux",+                    "type": "mrkdwn"+                }+            ],+            "text": {+                "text": "Section with text and fields",+                "type": "mrkdwn"+            },+            "type": "section"+        },+        {             "elements": [                 {                     "text": ":key: Context!",
tests/golden/SlackWebhookEvent/botMessage.golden view
@@ -37,6 +37,7 @@                 , threadTs = Just "1664216109.798919"                 , appId = Just "A0442TUPHGR"                 , botId = Just "B0439P161B9"+                , attachments = Nothing                 }             )         }
+ tests/golden/SlackWebhookEvent/circleci.golden view
@@ -0,0 +1,57 @@+EventEventCallback+    ( EventCallback+        { eventId = EventId+            { unEventId = "Ev0000000000" }+        , teamId = TeamId+            { unTeamId = "T0123456789" }+        , eventTime = MkSystemTime+            { systemSeconds = 1671046987+            , systemNanoseconds = 0+            }+        , event = EventMessage+            ( MessageEvent+                { blocks = Just+                    [ SlackPlainTextOnly "Job Failed. :red_circle:"+                    , SlackSection+                        { slackSectionText = Nothing+                        , slackSectionBlockId = Just+                            ( NonEmptyText "jO2E" )+                        , slackSectionFields = Just [ "*Job*: something" ]+                        , slackSectionAccessory = Nothing+                        }+                    , SlackSection+                        { slackSectionText = Nothing+                        , slackSectionBlockId = Just+                            ( NonEmptyText "adB9" )+                        , slackSectionFields = Just+                            [ "*Project*: something"+                            , "*Branch*: master"+                            , "*Author*: someone"+                            ]+                        , slackSectionAccessory = Nothing+                        }+                    , SlackSection+                        { slackSectionText = Nothing+                        , slackSectionBlockId = Just+                            ( NonEmptyText "=sdEQ" )+                        , slackSectionFields = Just [ "*Mentions*: <!subteam^S0123468791|@someone>" ]+                        , slackSectionAccessory = Nothing+                        }+                    , "actions(Just (NonEmptyText "eeK")) = ["SlackActionId {unSlackActionId = NonEmptyText \"zHNw\"} [button SlackButtonText (NonEmptyText \"View Job\")]"]"+                    ]+                , channel = ConversationId+                    { unConversationId = "C012345678" }+                , text = "CircleCI job failed."+                , channelType = Channel+                , files = Nothing+                , user = UserId+                    { unUserId = "U04130YGVK8" }+                , ts = "1671046987.237089"+                , threadTs = Nothing+                , appId = Just "A56789123"+                , botId = Just "B01234687"+                , attachments = Nothing+                }+            )+        }+    )
+ tests/golden/SlackWebhookEvent/circleci.json view
@@ -0,0 +1,119 @@+{+  "token": "aaaa",+  "team_id": "T0123456789",+  "api_app_id": "A12346789",+  "event": {+    "bot_id": "B01234687",+    "type": "message",+    "text": "CircleCI job failed.",+    "user": "U04130YGVK8",+    "ts": "1671046987.237089",+    "app_id": "A56789123",+    "blocks": [+      {+        "type": "header",+        "block_id": "+6hb",+        "text": {+          "type": "plain_text",+          "text": "Job Failed. :red_circle:",+          "emoji": true+        }+      },+      {+        "type": "section",+        "block_id": "jO2E",+        "fields": [+          {+            "type": "mrkdwn",+            "text": "*Job*: something",+            "verbatim": false+          }+        ]+      },+      {+        "type": "section",+        "block_id": "adB9",+        "accessory": {+          "type": "image",+          "image_url": "https://example.com",+          "alt_text": "CircleCI logo"+        },+        "fields": [+          {+            "type": "mrkdwn",+            "text": "*Project*: something",+            "verbatim": false+          },+          {+            "type": "mrkdwn",+            "text": "*Branch*: master",+            "verbatim": false+          },+          {+            "type": "mrkdwn",+            "text": "*Author*: someone",+            "verbatim": false+          }+        ]+      },+      {+        "type": "section",+        "block_id": "=sdEQ",+        "fields": [+          {+            "type": "mrkdwn",+            "text": "*Mentions*: <!subteam^S0123468791|@someone>",+            "verbatim": false+          }+        ]+      },+      {+        "type": "actions",+        "block_id": "eeK",+        "elements": [+          {+            "type": "button",+            "action_id": "zHNw",+            "text": {+              "type": "plain_text",+              "text": "View Job",+              "emoji": true+            },+            "url": "https://circleci.com/gh/aaaaaaaaaaaaaaaaaaa/aaaaaaaaaaa/000000"+          }+        ]+      }+    ],+    "team": "T0123456789",+    "bot_profile": {+      "id": "B01234687",+      "deleted": false,+      "name": "CircleCI integration",+      "updated": 1662492349,+      "app_id": "A56789123",+      "icons": {+        "image_36": "https://example.com",+        "image_48": "https://example.com",+        "image_72": "https://example.com"+      },+      "team_id": "T0123456789"+    },+    "channel": "C012345678",+    "event_ts": "1671046987.237089",+    "channel_type": "channel"+  },+  "type": "event_callback",+  "event_id": "Ev0000000000",+  "event_time": 1671046987,+  "authorizations": [+    {+      "enterprise_id": null,+      "team_id": "T0123456789",+      "user_id": "U01234678",+      "is_bot": true,+      "is_enterprise_install": false+    }+  ],+  "is_ext_shared_channel": false,+  "event_context": "aaaa"+}
tests/golden/SlackWebhookEvent/email_message.golden view
@@ -37,6 +37,7 @@                 , threadTs = Nothing                 , appId = Nothing                 , botId = Just "B012346789"+                , attachments = Nothing                 }             )         }
+ tests/golden/SlackWebhookEvent/forwarded_message.golden view
@@ -0,0 +1,238 @@+EventEventCallback+    ( EventCallback+        { eventId = EventId+            { unEventId = "Ev07KHG83J1F" }+        , teamId = TeamId+            { unTeamId = "T043DB835ML" }+        , eventTime = MkSystemTime+            { systemSeconds = 1724952300+            , systemNanoseconds = 0+            }+        , event = EventMessage+            ( MessageEvent+                { blocks = Nothing+                , channel = ConversationId+                    { unConversationId = "C07KHDGQ7K3" }+                , text = ""+                , channelType = Channel+                , files = Nothing+                , user = UserId+                    { unUserId = "U05JAA9EN4T" }+                , ts = "1724952300.110719"+                , threadTs = Nothing+                , appId = Nothing+                , botId = Nothing+                , attachments = Just+                    [ MessageAttachment+                        { decoded = Just+                            ( DecodedMessageAttachment+                                { fallback = Just "[August 29th, 2024 10:24 AM] lev: 123"+                                , color = Just "D0D0D0"+                                , pretext = Nothing+                                , authorName = Just "lev"+                                , authorLink = Just "https://myworkspace.slack.com/team/U05JAA9EN4T"+                                , authorIcon = Just "https://avatars.slack-edge.com/2023-07-22/5607926127815_66b9d49327ec16a887e5_48.png"+                                , title = Nothing+                                , titleLink = Nothing+                                , text = Just "123"+                                , fields = Nothing+                                , imageUrl = Nothing+                                , thumbUrl = Nothing+                                , footer = Just "Slack Conversation"+                                , footerIcon = Nothing+                                , ts = Just "1724952281.623809"+                                , isMsgUnfurl = Just True+                                , messageBlocks = Just+                                    [ AttachmentMessageBlock+                                        { team = TeamId+                                            { unTeamId = "T043DB835ML" }+                                        , channel = ConversationId+                                            { unConversationId = "C07KTH1T4CQ" }+                                        , ts = "1724952281.623809"+                                        , message = AttachmentMessageBlockMessage+                                            { blocks =+                                                [ RichText+                                                    { blockId = Just+                                                        ( NonEmptyText "nXUog" )+                                                    , elements =+                                                        [ RichTextSectionItemRichText+                                                            [ RichItemText "123"+                                                                ( RichStyle+                                                                    { rsBold = False+                                                                    , rsItalic = False+                                                                    }+                                                                )+                                                            ]+                                                        ]+                                                    }+                                                ]+                                            }+                                        }+                                    ]+                                , authorId = Just+                                    ( UserId+                                        { unUserId = "U05JAA9EN4T" }+                                    )+                                , channelId = Just+                                    ( ConversationId+                                        { unConversationId = "C07KTH1T4CQ" }+                                    )+                                , channelTeam = Just+                                    ( TeamId+                                        { unTeamId = "T043DB835ML" }+                                    )+                                , isAppUnfurl = Nothing+                                , appUnfurlUrl = Nothing+                                , fromUrl = Just "https://myworkspace.slack.com/archives/C07KTH1T4CQ/p1724952281623809"+                                }+                            )+                        , raw = Object+                            ( fromList+                                [+                                    ( "author_icon"+                                    , String "https://avatars.slack-edge.com/2023-07-22/5607926127815_66b9d49327ec16a887e5_48.png"+                                    )+                                ,+                                    ( "author_id"+                                    , String "U05JAA9EN4T"+                                    )+                                ,+                                    ( "author_link"+                                    , String "https://myworkspace.slack.com/team/U05JAA9EN4T"+                                    )+                                ,+                                    ( "author_name"+                                    , String "lev"+                                    )+                                ,+                                    ( "author_subname"+                                    , String "lev"+                                    )+                                ,+                                    ( "channel_id"+                                    , String "C07KTH1T4CQ"+                                    )+                                ,+                                    ( "channel_team"+                                    , String "T043DB835ML"+                                    )+                                ,+                                    ( "color"+                                    , String "D0D0D0"+                                    )+                                ,+                                    ( "fallback"+                                    , String "[August 29th, 2024 10:24 AM] lev: 123"+                                    )+                                ,+                                    ( "footer"+                                    , String "Slack Conversation"+                                    )+                                ,+                                    ( "from_url"+                                    , String "https://myworkspace.slack.com/archives/C07KTH1T4CQ/p1724952281623809"+                                    )+                                ,+                                    ( "is_msg_unfurl"+                                    , Bool True+                                    )+                                ,+                                    ( "is_share"+                                    , Bool True+                                    )+                                ,+                                    ( "message_blocks"+                                    , Array+                                        [ Object+                                            ( fromList+                                                [+                                                    ( "channel"+                                                    , String "C07KTH1T4CQ"+                                                    )+                                                ,+                                                    ( "message"+                                                    , Object+                                                        ( fromList+                                                            [+                                                                ( "blocks"+                                                                , Array+                                                                    [ Object+                                                                        ( fromList+                                                                            [+                                                                                ( "block_id"+                                                                                , String "nXUog"+                                                                                )+                                                                            ,+                                                                                ( "elements"+                                                                                , Array+                                                                                    [ Object+                                                                                        ( fromList+                                                                                            [+                                                                                                ( "elements"+                                                                                                , Array+                                                                                                    [ Object+                                                                                                        ( fromList+                                                                                                            [+                                                                                                                ( "text"+                                                                                                                , String "123"+                                                                                                                )+                                                                                                            ,+                                                                                                                ( "type"+                                                                                                                , String "text"+                                                                                                                )+                                                                                                            ]+                                                                                                        )+                                                                                                    ]+                                                                                                )+                                                                                            ,+                                                                                                ( "type"+                                                                                                , String "rich_text_section"+                                                                                                )+                                                                                            ]+                                                                                        )+                                                                                    ]+                                                                                )+                                                                            ,+                                                                                ( "type"+                                                                                , String "rich_text"+                                                                                )+                                                                            ]+                                                                        )+                                                                    ]+                                                                )+                                                            ]+                                                        )+                                                    )+                                                ,+                                                    ( "team"+                                                    , String "T043DB835ML"+                                                    )+                                                ,+                                                    ( "ts"+                                                    , String "1724952281.623809"+                                                    )+                                                ]+                                            )+                                        ]+                                    )+                                ,+                                    ( "mrkdwn_in"+                                    , Array+                                        [ String "text" ]+                                    )+                                ,+                                    ( "text"+                                    , String "123"+                                    )+                                ,+                                    ( "ts"+                                    , String "1724952281.623809"+                                    )+                                ]+                            )+                        }+                    ]+                }+            )+        }+    )
+ tests/golden/SlackWebhookEvent/forwarded_message.json view
@@ -0,0 +1,78 @@+{+    "token": "aaaa",+    "team_id": "T043DB835ML",+    "context_team_id": "T043DB835ML",+    "context_enterprise_id": null,+    "api_app_id": "A07JRKQPZDH",+    "event": {+        "user": "U05JAA9EN4T",+        "type": "message",+        "ts": "1724952300.110719",+        "text": "",+        "team": "T043DB835ML",+        "attachments": [+            {+                "ts": "1724952281.623809",+                "author_id": "U05JAA9EN4T",+                "channel_id": "C07KTH1T4CQ",+                "channel_team": "T043DB835ML",+                "is_msg_unfurl": true,+                "message_blocks": [+                    {+                        "team": "T043DB835ML",+                        "channel": "C07KTH1T4CQ",+                        "ts": "1724952281.623809",+                        "message": {+                            "blocks": [+                                {+                                    "type": "rich_text",+                                    "block_id": "nXUog",+                                    "elements": [+                                        {+                                            "type": "rich_text_section",+                                            "elements": [+                                                {+                                                    "type": "text",+                                                    "text": "123"+                                                }+                                            ]+                                        }+                                    ]+                                }+                            ]+                        }+                    }+                ],+                "color": "D0D0D0",+                "from_url": "https://myworkspace.slack.com/archives/C07KTH1T4CQ/p1724952281623809",+                "is_share": true,+                "fallback": "[August 29th, 2024 10:24 AM] lev: 123",+                "text": "123",+                "author_name": "lev",+                "author_link": "https://myworkspace.slack.com/team/U05JAA9EN4T",+                "author_icon": "https://avatars.slack-edge.com/2023-07-22/5607926127815_66b9d49327ec16a887e5_48.png",+                "author_subname": "lev",+                "mrkdwn_in": [+                    "text"+                ],+                "footer": "Slack Conversation"+            }+        ],+        "channel": "C07KHDGQ7K3",+        "event_ts": "1724952300.110719",+        "channel_type": "channel"+    },+    "type": "event_callback",+    "event_id": "Ev07KHG83J1F",+    "event_time": 1724952300,+    "authorizations": [+        {+            "enterprise_id": null,+            "team_id": "T043DB835ML",+            "user_id": "U07K4E18KJM",+            "is_bot": true,+            "is_enterprise_install": false+        }+    ],+    "is_ext_shared_channel": false+}
+ tests/golden/SlackWebhookEvent/github_notification.golden view
@@ -0,0 +1,197 @@+EventEventCallback+    ( EventCallback+        { eventId = EventId+            { unEventId = "Ev07MH6R7FDE" }+        , teamId = TeamId+            { unTeamId = "T043DB835ML" }+        , eventTime = MkSystemTime+            { systemSeconds = 1725998548+            , systemNanoseconds = 0+            }+        , event = EventMessage+            ( MessageEvent+                { blocks = Nothing+                , channel = ConversationId+                    { unConversationId = "C07LRFB3C8M" }+                , text = ""+                , channelType = Channel+                , files = Nothing+                , user = UserId+                    { unUserId = "U07M725KAD7" }+                , ts = "1725998548.581159"+                , threadTs = Nothing+                , appId = Just "A01BP7R4KNY"+                , botId = Just "B07LDR01Z63"+                , attachments = Just+                    [ MessageAttachment+                        { decoded = Just+                            ( DecodedMessageAttachment+                                { fallback = Just "[myorg/myrepo] Issue opened by ldub"+                                , color = Just "36a64f"+                                , pretext = Just "Issue created by <https://github.com/ldub|ldub>"+                                , authorName = Nothing+                                , authorLink = Nothing+                                , authorIcon = Nothing+                                , title = Just "<https://github.com/myorg/myrepo/issues/2|#2 test issue 2>"+                                , titleLink = Nothing+                                , text = Just "another oneeeee"+                                , fields = Nothing+                                , imageUrl = Nothing+                                , thumbUrl = Nothing+                                , footer = Just "<https://github.com/myorg/myrepo|myorg/myrepo>"+                                , footerIcon = Just "https://slack.github.com/static/img/favicon-neutral.png"+                                , ts = Just "1725998545.0"+                                , isMsgUnfurl = Nothing+                                , messageBlocks = Nothing+                                , authorId = Nothing+                                , channelId = Nothing+                                , channelTeam = Nothing+                                , isAppUnfurl = Nothing+                                , appUnfurlUrl = Nothing+                                , fromUrl = Nothing+                                }+                            )+                        , raw = Object+                            ( fromList+                                [+                                    ( "actions"+                                    , Array+                                        [ Object+                                            ( fromList+                                                [+                                                    ( "id"+                                                    , String "1"+                                                    )+                                                ,+                                                    ( "name"+                                                    , String "comment"+                                                    )+                                                ,+                                                    ( "style"+                                                    , String ""+                                                    )+                                                ,+                                                    ( "text"+                                                    , String "Comment"+                                                    )+                                                ,+                                                    ( "type"+                                                    , String "button"+                                                    )+                                                ,+                                                    ( "value"+                                                    , String "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}"+                                                    )+                                                ]+                                            )+                                        , Object+                                            ( fromList+                                                [+                                                    ( "id"+                                                    , String "2"+                                                    )+                                                ,+                                                    ( "name"+                                                    , String "edit"+                                                    )+                                                ,+                                                    ( "style"+                                                    , String ""+                                                    )+                                                ,+                                                    ( "text"+                                                    , String "Edit"+                                                    )+                                                ,+                                                    ( "type"+                                                    , String "button"+                                                    )+                                                ,+                                                    ( "value"+                                                    , String "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}"+                                                    )+                                                ]+                                            )+                                        , Object+                                            ( fromList+                                                [+                                                    ( "id"+                                                    , String "3"+                                                    )+                                                ,+                                                    ( "name"+                                                    , String "close"+                                                    )+                                                ,+                                                    ( "style"+                                                    , String "danger"+                                                    )+                                                ,+                                                    ( "text"+                                                    , String "Close"+                                                    )+                                                ,+                                                    ( "type"+                                                    , String "button"+                                                    )+                                                ,+                                                    ( "value"+                                                    , String "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}"+                                                    )+                                                ]+                                            )+                                        ]+                                    )+                                ,+                                    ( "callback_id"+                                    , String "issue-opened-interaction"+                                    )+                                ,+                                    ( "color"+                                    , String "36a64f"+                                    )+                                ,+                                    ( "fallback"+                                    , String "[myorg/myrepo] Issue opened by ldub"+                                    )+                                ,+                                    ( "footer"+                                    , String "<https://github.com/myorg/myrepo|myorg/myrepo>"+                                    )+                                ,+                                    ( "footer_icon"+                                    , String "https://slack.github.com/static/img/favicon-neutral.png"+                                    )+                                ,+                                    ( "id"+                                    , Number 1.0+                                    )+                                ,+                                    ( "mrkdwn_in"+                                    , Array+                                        [ String "text" ]+                                    )+                                ,+                                    ( "pretext"+                                    , String "Issue created by <https://github.com/ldub|ldub>"+                                    )+                                ,+                                    ( "text"+                                    , String "another oneeeee"+                                    )+                                ,+                                    ( "title"+                                    , String "<https://github.com/myorg/myrepo/issues/2|#2 test issue 2>"+                                    )+                                ,+                                    ( "ts"+                                    , Number 1.725998545e9+                                    )+                                ]+                            )+                        }+                    ]+                }+            )+        }+    )
+ tests/golden/SlackWebhookEvent/github_notification.json view
@@ -0,0 +1,88 @@+{+    "token": "aaaa",+    "team_id": "T043DB835ML",+    "context_team_id": "T043DB835ML",+    "context_enterprise_id": null,+    "api_app_id": "A07JRKQPZDH",+    "event": {+        "user": "U07M725KAD7",+        "type": "message",+        "ts": "1725998548.581159",+        "bot_id": "B07LDR01Z63",+        "app_id": "A01BP7R4KNY",+        "text": "",+        "team": "T043DB835ML",+        "bot_profile": {+            "id": "B07LDR01Z63",+            "deleted": false,+            "name": "GitHub",+            "updated": 1725998199,+            "app_id": "A01BP7R4KNY",+            "icons": {+                "image_36": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_36.png",+                "image_48": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_48.png",+                "image_72": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_72.png"+            },+            "team_id": "T043DB835ML"+        },+        "attachments": [+            {+                "id": 1,+                "footer_icon": "https://slack.github.com/static/img/favicon-neutral.png",+                "ts": 1725998545,+                "color": "36a64f",+                "fallback": "[myorg/myrepo] Issue opened by ldub",+                "text": "another oneeeee",+                "pretext": "Issue created by <https://github.com/ldub|ldub>",+                "title": "<https://github.com/myorg/myrepo/issues/2|#2 test issue 2>",+                "callback_id": "issue-opened-interaction",+                "footer": "<https://github.com/myorg/myrepo|myorg/myrepo>",+                "mrkdwn_in": [+                    "text"+                ],+                "actions": [+                    {+                        "id": "1",+                        "name": "comment",+                        "text": "Comment",+                        "type": "button",+                        "value": "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}",+                        "style": ""+                    },+                    {+                        "id": "2",+                        "name": "edit",+                        "text": "Edit",+                        "type": "button",+                        "value": "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}",+                        "style": ""+                    },+                    {+                        "id": "3",+                        "name": "close",+                        "text": "Close",+                        "type": "button",+                        "value": "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}",+                        "style": "danger"+                    }+                ]+            }+        ],+        "channel": "C07LRFB3C8M",+        "event_ts": "1725998548.581159",+        "channel_type": "channel"+    },+    "type": "event_callback",+    "event_id": "Ev07MH6R7FDE",+    "event_time": 1725998548,+    "authorizations": [+        {+            "enterprise_id": null,+            "team_id": "T043DB835ML",+            "user_id": "U07K4E18KJM",+            "is_bot": true,+            "is_enterprise_install": false+        }+    ],+    "is_ext_shared_channel": false+}
+ tests/golden/SlackWebhookEvent/github_notification_ts_string.golden view
@@ -0,0 +1,197 @@+EventEventCallback+    ( EventCallback+        { eventId = EventId+            { unEventId = "Ev07MH6R7FDE" }+        , teamId = TeamId+            { unTeamId = "T043DB835ML" }+        , eventTime = MkSystemTime+            { systemSeconds = 1725998548+            , systemNanoseconds = 0+            }+        , event = EventMessage+            ( MessageEvent+                { blocks = Nothing+                , channel = ConversationId+                    { unConversationId = "C07LRFB3C8M" }+                , text = ""+                , channelType = Channel+                , files = Nothing+                , user = UserId+                    { unUserId = "U07M725KAD7" }+                , ts = "1725998548.581159"+                , threadTs = Nothing+                , appId = Just "A01BP7R4KNY"+                , botId = Just "B07LDR01Z63"+                , attachments = Just+                    [ MessageAttachment+                        { decoded = Just+                            ( DecodedMessageAttachment+                                { fallback = Just "[myorg/myrepo] Issue opened by ldub"+                                , color = Just "36a64f"+                                , pretext = Just "Issue created by <https://github.com/ldub|ldub>"+                                , authorName = Nothing+                                , authorLink = Nothing+                                , authorIcon = Nothing+                                , title = Just "<https://github.com/myorg/myrepo/issues/2|#2 test issue 2>"+                                , titleLink = Nothing+                                , text = Just "another oneeeee"+                                , fields = Nothing+                                , imageUrl = Nothing+                                , thumbUrl = Nothing+                                , footer = Just "<https://github.com/myorg/myrepo|myorg/myrepo>"+                                , footerIcon = Just "https://slack.github.com/static/img/favicon-neutral.png"+                                , ts = Just "1725998545.123456"+                                , isMsgUnfurl = Nothing+                                , messageBlocks = Nothing+                                , authorId = Nothing+                                , channelId = Nothing+                                , channelTeam = Nothing+                                , isAppUnfurl = Nothing+                                , appUnfurlUrl = Nothing+                                , fromUrl = Nothing+                                }+                            )+                        , raw = Object+                            ( fromList+                                [+                                    ( "actions"+                                    , Array+                                        [ Object+                                            ( fromList+                                                [+                                                    ( "id"+                                                    , String "1"+                                                    )+                                                ,+                                                    ( "name"+                                                    , String "comment"+                                                    )+                                                ,+                                                    ( "style"+                                                    , String ""+                                                    )+                                                ,+                                                    ( "text"+                                                    , String "Comment"+                                                    )+                                                ,+                                                    ( "type"+                                                    , String "button"+                                                    )+                                                ,+                                                    ( "value"+                                                    , String "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}"+                                                    )+                                                ]+                                            )+                                        , Object+                                            ( fromList+                                                [+                                                    ( "id"+                                                    , String "2"+                                                    )+                                                ,+                                                    ( "name"+                                                    , String "edit"+                                                    )+                                                ,+                                                    ( "style"+                                                    , String ""+                                                    )+                                                ,+                                                    ( "text"+                                                    , String "Edit"+                                                    )+                                                ,+                                                    ( "type"+                                                    , String "button"+                                                    )+                                                ,+                                                    ( "value"+                                                    , String "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}"+                                                    )+                                                ]+                                            )+                                        , Object+                                            ( fromList+                                                [+                                                    ( "id"+                                                    , String "3"+                                                    )+                                                ,+                                                    ( "name"+                                                    , String "close"+                                                    )+                                                ,+                                                    ( "style"+                                                    , String "danger"+                                                    )+                                                ,+                                                    ( "text"+                                                    , String "Close"+                                                    )+                                                ,+                                                    ( "type"+                                                    , String "button"+                                                    )+                                                ,+                                                    ( "value"+                                                    , String "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}"+                                                    )+                                                ]+                                            )+                                        ]+                                    )+                                ,+                                    ( "callback_id"+                                    , String "issue-opened-interaction"+                                    )+                                ,+                                    ( "color"+                                    , String "36a64f"+                                    )+                                ,+                                    ( "fallback"+                                    , String "[myorg/myrepo] Issue opened by ldub"+                                    )+                                ,+                                    ( "footer"+                                    , String "<https://github.com/myorg/myrepo|myorg/myrepo>"+                                    )+                                ,+                                    ( "footer_icon"+                                    , String "https://slack.github.com/static/img/favicon-neutral.png"+                                    )+                                ,+                                    ( "id"+                                    , Number 1.0+                                    )+                                ,+                                    ( "mrkdwn_in"+                                    , Array+                                        [ String "text" ]+                                    )+                                ,+                                    ( "pretext"+                                    , String "Issue created by <https://github.com/ldub|ldub>"+                                    )+                                ,+                                    ( "text"+                                    , String "another oneeeee"+                                    )+                                ,+                                    ( "title"+                                    , String "<https://github.com/myorg/myrepo/issues/2|#2 test issue 2>"+                                    )+                                ,+                                    ( "ts"+                                    , String "1725998545.123456"+                                    )+                                ]+                            )+                        }+                    ]+                }+            )+        }+    )
+ tests/golden/SlackWebhookEvent/github_notification_ts_string.json view
@@ -0,0 +1,88 @@+{+    "token": "aaaa",+    "team_id": "T043DB835ML",+    "context_team_id": "T043DB835ML",+    "context_enterprise_id": null,+    "api_app_id": "A07JRKQPZDH",+    "event": {+        "user": "U07M725KAD7",+        "type": "message",+        "ts": "1725998548.581159",+        "bot_id": "B07LDR01Z63",+        "app_id": "A01BP7R4KNY",+        "text": "",+        "team": "T043DB835ML",+        "bot_profile": {+            "id": "B07LDR01Z63",+            "deleted": false,+            "name": "GitHub",+            "updated": 1725998199,+            "app_id": "A01BP7R4KNY",+            "icons": {+                "image_36": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_36.png",+                "image_48": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_48.png",+                "image_72": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_72.png"+            },+            "team_id": "T043DB835ML"+        },+        "attachments": [+            {+                "id": 1,+                "footer_icon": "https://slack.github.com/static/img/favicon-neutral.png",+                "ts": "1725998545.123456",+                "color": "36a64f",+                "fallback": "[myorg/myrepo] Issue opened by ldub",+                "text": "another oneeeee",+                "pretext": "Issue created by <https://github.com/ldub|ldub>",+                "title": "<https://github.com/myorg/myrepo/issues/2|#2 test issue 2>",+                "callback_id": "issue-opened-interaction",+                "footer": "<https://github.com/myorg/myrepo|myorg/myrepo>",+                "mrkdwn_in": [+                    "text"+                ],+                "actions": [+                    {+                        "id": "1",+                        "name": "comment",+                        "text": "Comment",+                        "type": "button",+                        "value": "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}",+                        "style": ""+                    },+                    {+                        "id": "2",+                        "name": "edit",+                        "text": "Edit",+                        "type": "button",+                        "value": "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}",+                        "style": ""+                    },+                    {+                        "id": "3",+                        "name": "close",+                        "text": "Close",+                        "type": "button",+                        "value": "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:809927288,&amp;amp;quot;issueNumber&amp;amp;quot;:2,&amp;amp;quot;issueHtmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/issues/2&amp;amp;quot;,&amp;amp;quot;issueTitle&amp;amp;quot;:&amp;amp;quot;test issue 2&amp;amp;quot;}",+                        "style": "danger"+                    }+                ]+            }+        ],+        "channel": "C07LRFB3C8M",+        "event_ts": "1725998548.581159",+        "channel_type": "channel"+    },+    "type": "event_callback",+    "event_id": "Ev07MH6R7FDE",+    "event_time": 1725998548,+    "authorizations": [+        {+            "enterprise_id": null,+            "team_id": "T043DB835ML",+            "user_id": "U07K4E18KJM",+            "is_bot": true,+            "is_enterprise_install": false+        }+    ],+    "is_ext_shared_channel": false+}
+ tests/golden/SlackWebhookEvent/github_with_link.golden view
@@ -0,0 +1,145 @@+EventEventCallback+    ( EventCallback+        { eventId = EventId+            { unEventId = "Ev07U5U3EQCV" }+        , teamId = TeamId+            { unTeamId = "T043DB835ML" }+        , eventTime = MkSystemTime+            { systemSeconds = 1730339959+            , systemNanoseconds = 0+            }+        , event = EventMessage+            ( MessageEvent+                { blocks = Nothing+                , channel = ConversationId+                    { unConversationId = "C07LRFB3C8M" }+                , text = ""+                , channelType = Channel+                , files = Nothing+                , user = UserId+                    { unUserId = "U07M725KAD7" }+                , ts = "1730339959.644979"+                , threadTs = Nothing+                , appId = Just "A01BP7R4KNY"+                , botId = Just "B07LDR01Z63"+                , attachments = Just+                    [ MessageAttachment+                        { decoded = Just+                            ( DecodedMessageAttachment+                                { fallback = Just "[myorg/myrepo] Pull request opened by ldub"+                                , color = Just "36a64f"+                                , pretext = Just "Pull request opened by <https://github.com/ldub|ldub>"+                                , authorName = Nothing+                                , authorLink = Nothing+                                , authorIcon = Nothing+                                , title = Just "<https://github.com/myorg/myrepo/pull/8|#8 https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249>"+                                , titleLink = Nothing+                                , text = Just "this is a fake pr with a link++                                  <https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249|https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249>"+                                , fields = Nothing+                                , imageUrl = Nothing+                                , thumbUrl = Nothing+                                , footer = Just "<https://github.com/myorg/myrepo|myorg/myrepo>"+                                , footerIcon = Just "https://slack.github.com/static/img/favicon-neutral.png"+                                , ts = Just "1730339957.0"+                                , isMsgUnfurl = Nothing+                                , messageBlocks = Nothing+                                , authorId = Nothing+                                , channelId = Nothing+                                , channelTeam = Nothing+                                , isAppUnfurl = Nothing+                                , appUnfurlUrl = Nothing+                                , fromUrl = Nothing+                                }+                            )+                        , raw = Object+                            ( fromList+                                [+                                    ( "actions"+                                    , Array+                                        [ Object+                                            ( fromList+                                                [+                                                    ( "id"+                                                    , String "1"+                                                    )+                                                ,+                                                    ( "name"+                                                    , String "comment"+                                                    )+                                                ,+                                                    ( "style"+                                                    , String ""+                                                    )+                                                ,+                                                    ( "text"+                                                    , String "Comment"+                                                    )+                                                ,+                                                    ( "type"+                                                    , String "button"+                                                    )+                                                ,+                                                    ( "value"+                                                    , String "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:669978765,&amp;amp;quot;number&amp;amp;quot;:8,&amp;amp;quot;htmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/pull/8&amp;amp;quot;,&amp;amp;quot;title&amp;amp;quot;:&amp;amp;quot;https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249&amp;amp;quot;}"+                                                    )+                                                ]+                                            )+                                        ]+                                    )+                                ,+                                    ( "callback_id"+                                    , String "pr-opened-interaction"+                                    )+                                ,+                                    ( "color"+                                    , String "36a64f"+                                    )+                                ,+                                    ( "fallback"+                                    , String "[myorg/myrepo] Pull request opened by ldub"+                                    )+                                ,+                                    ( "footer"+                                    , String "<https://github.com/myorg/myrepo|myorg/myrepo>"+                                    )+                                ,+                                    ( "footer_icon"+                                    , String "https://slack.github.com/static/img/favicon-neutral.png"+                                    )+                                ,+                                    ( "id"+                                    , Number 1.0+                                    )+                                ,+                                    ( "mrkdwn_in"+                                    , Array+                                        [ String "text" ]+                                    )+                                ,+                                    ( "pretext"+                                    , String "Pull request opened by <https://github.com/ldub|ldub>"+                                    )+                                ,+                                    ( "text"+                                    , String "this is a fake pr with a link++                                      <https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249|https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249>"+                                    )+                                ,+                                    ( "title"+                                    , String "<https://github.com/myorg/myrepo/pull/8|#8 https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249>"+                                    )+                                ,+                                    ( "ts"+                                    , Number 1.730339957e9+                                    )+                                ]+                            )+                        }+                    ]+                }+            )+        }+    )
+ tests/golden/SlackWebhookEvent/github_with_link.json view
@@ -0,0 +1,72 @@+{+    "token": "aaaa",+    "team_id": "T043DB835ML",+    "context_team_id": "T043DB835ML",+    "context_enterprise_id": null,+    "api_app_id": "A07JRKQPZDH",+    "event": {+        "user": "U07M725KAD7",+        "type": "message",+        "ts": "1730339959.644979",+        "bot_id": "B07LDR01Z63",+        "app_id": "A01BP7R4KNY",+        "text": "",+        "team": "T043DB835ML",+        "bot_profile": {+            "id": "B07LDR01Z63",+            "deleted": false,+            "name": "GitHub",+            "updated": 1725998199,+            "app_id": "A01BP7R4KNY",+            "icons": {+                "image_36": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_36.png",+                "image_48": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_48.png",+                "image_72": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_72.png"+            },+            "team_id": "T043DB835ML"+        },+        "attachments": [+            {+                "id": 1,+                "footer_icon": "https://slack.github.com/static/img/favicon-neutral.png",+                "ts": 1730339957,+                "color": "36a64f",+                "fallback": "[myorg/myrepo] Pull request opened by ldub",+                "text": "this is a fake pr with a link\n\n<https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249|https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249>",+                "pretext": "Pull request opened by <https://github.com/ldub|ldub>",+                "title": "<https://github.com/myorg/myrepo/pull/8|#8 https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249>",+                "callback_id": "pr-opened-interaction",+                "footer": "<https://github.com/myorg/myrepo|myorg/myrepo>",+                "mrkdwn_in": [+                    "text"+                ],+                "actions": [+                    {+                        "id": "1",+                        "name": "comment",+                        "text": "Comment",+                        "type": "button",+                        "value": "{&amp;amp;quot;selectedOrg&amp;amp;quot;:&amp;amp;quot;myorg&amp;amp;quot;,&amp;amp;quot;selectedOrgId&amp;amp;quot;:140364112,&amp;amp;quot;selectedRepo&amp;amp;quot;:&amp;amp;quot;myrepo&amp;amp;quot;,&amp;amp;quot;selectedRepoId&amp;amp;quot;:669978765,&amp;amp;quot;number&amp;amp;quot;:8,&amp;amp;quot;htmlUrl&amp;amp;quot;:&amp;amp;quot;https://github.com/myorg/myrepo/pull/8&amp;amp;quot;,&amp;amp;quot;title&amp;amp;quot;:&amp;amp;quot;https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249&amp;amp;quot;}",+                        "style": ""+                    }+                ]+            }+        ],+        "channel": "C07LRFB3C8M",+        "event_ts": "1730339959.644979",+        "channel_type": "channel"+    },+    "type": "event_callback",+    "event_id": "Ev07U5U3EQCV",+    "event_time": 1730339959,+    "authorizations": [+        {+            "enterprise_id": null,+            "team_id": "T043DB835ML",+            "user_id": "U07K4E18KJM",+            "is_bot": true,+            "is_enterprise_install": false+        }+    ],+    "is_ext_shared_channel": false+}
tests/golden/SlackWebhookEvent/link.golden view
@@ -41,6 +41,7 @@                 , threadTs = Nothing                 , appId = Nothing                 , botId = Nothing+                , attachments = Nothing                 }             )         }
tests/golden/SlackWebhookEvent/messageExample.golden view
@@ -37,6 +37,7 @@                 , threadTs = Nothing                 , appId = Nothing                 , botId = Nothing+                , attachments = Nothing                 }             )         }
tests/golden/SlackWebhookEvent/messageIm.golden view
@@ -37,6 +37,7 @@                 , threadTs = Nothing                 , appId = Nothing                 , botId = Nothing+                , attachments = Nothing                 }             )         }
tests/golden/SlackWebhookEvent/message_file_share.golden view
@@ -52,6 +52,7 @@                 , threadTs = Nothing                 , appId = Nothing                 , botId = Nothing+                , attachments = Nothing                 }             )         }
tests/golden/SlackWebhookEvent/message_file_share_slack_connect.golden view
@@ -27,6 +27,7 @@                 , threadTs = Nothing                 , appId = Nothing                 , botId = Nothing+                , attachments = Nothing                 }             )         }
tests/golden/SlackWebhookEvent/message_rich_text.golden view
@@ -79,6 +79,7 @@                 , threadTs = Nothing                 , appId = Nothing                 , botId = Nothing+                , attachments = Nothing                 }             )         }
+ tests/golden/SlackWebhookEvent/message_subtype_bot_message.golden view
@@ -0,0 +1,46 @@+EventEventCallback+    ( EventCallback+        { eventId = EventId+            { unEventId = "Ev07JQHFTAKH" }+        , teamId = TeamId+            { unTeamId = "T043DB835ML" }+        , eventTime = MkSystemTime+            { systemSeconds = 1724955010+            , systemNanoseconds = 0+            }+        , event = EventBotMessage+            ( BotMessageEvent+                { blocks = Just+                    [ RichText+                        { blockId = Just+                            ( NonEmptyText "nmh" )+                        , elements =+                            [ RichTextSectionItemRichText+                                [ RichItemLink+                                    ( RichLinkAttrs+                                        { style = RichStyle+                                            { rsBold = False+                                            , rsItalic = False+                                            }+                                        , url = "https://myworkspace.slack.com/archives/C07KTH1T4CQ/p1724954997876929"+                                        , text = Nothing+                                        }+                                    )+                                ]+                            ]+                        }+                    ]+                , channel = ConversationId+                    { unConversationId = "C07KH38CR5E" }+                , text = "<https://myworkspace.slack.com/archives/C07KTH1T4CQ/p1724954997876929>"+                , channelType = Channel+                , files = Nothing+                , ts = "1724955010.440469"+                , threadTs = Nothing+                , appId = Just "A1BFDR28J"+                , botId = "B07JWPS5Y5S"+                , attachments = Nothing+                }+            )+        }+    )
+ tests/golden/SlackWebhookEvent/message_subtype_bot_message.json view
@@ -0,0 +1,53 @@+{+    "token": "d90zMGO2eDLrkLHJYcKevn2b",+    "team_id": "T043DB835ML",+    "context_team_id": "T043DB835ML",+    "context_enterprise_id": null,+    "api_app_id": "A07JRKQPZDH",+    "event": {+        "subtype": "bot_message",+        "text": "<https://myworkspace.slack.com/archives/C07KTH1T4CQ/p1724954997876929>",+        "username": "Reacji Channeler",+        "icons": {+            "emoji": "bulb"+        },+        "type": "message",+        "ts": "1724955010.440469",+        "bot_id": "B07JWPS5Y5S",+        "app_id": "A1BFDR28J",+        "blocks": [+            {+                "type": "rich_text",+                "block_id": "nmh",+                "elements": [+                    {+                        "type": "rich_text_section",+                        "elements": [+                            {+                                "type": "link",+                                "url": "https://myworkspace.slack.com/archives/C07KTH1T4CQ/p1724954997876929"+                            }+                        ]+                    }+                ]+            }+        ],+        "channel": "C07KH38CR5E",+        "event_ts": "1724955010.440469",+        "channel_type": "channel"+    },+    "type": "event_callback",+    "event_id": "Ev07JQHFTAKH",+    "event_time": 1724955010,+    "authorizations": [+        {+            "enterprise_id": null,+            "team_id": "T043DB835ML",+            "user_id": "U07K4E18KJM",+            "is_bot": true,+            "is_enterprise_install": false+        }+    ],+    "is_ext_shared_channel": false,+    "event_context": "4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUMDQzREI4MzVNTCIsImFpZCI6IkEwN0pSS1FQWkRIIiwiY2lkIjoiQzA3S0gzOENSNUUifQ=="+}
+ tests/golden/SlackWebhookEvent/non_spec_attachment.golden view
@@ -0,0 +1,99 @@+EventEventCallback+    ( EventCallback+        { eventId = EventId+            { unEventId = "Ev07MH6R7FDE" }+        , teamId = TeamId+            { unTeamId = "T043DB835ML" }+        , eventTime = MkSystemTime+            { systemSeconds = 1725998548+            , systemNanoseconds = 0+            }+        , event = EventMessage+            ( MessageEvent+                { blocks = Nothing+                , channel = ConversationId+                    { unConversationId = "C07LRFB3C8M" }+                , text = ""+                , channelType = Channel+                , files = Nothing+                , user = UserId+                    { unUserId = "U07M725KAD7" }+                , ts = "1725998548.581159"+                , threadTs = Nothing+                , appId = Just "A01BP7R4KNY"+                , botId = Just "B07LDR01Z63"+                , attachments = Just+                    [ MessageAttachment+                        { decoded = Just+                            ( DecodedMessageAttachment+                                { fallback = Nothing+                                , color = Nothing+                                , pretext = Nothing+                                , authorName = Nothing+                                , authorLink = Nothing+                                , authorIcon = Nothing+                                , title = Nothing+                                , titleLink = Nothing+                                , text = Nothing+                                , fields = Nothing+                                , imageUrl = Nothing+                                , thumbUrl = Nothing+                                , footer = Nothing+                                , footerIcon = Nothing+                                , ts = Nothing+                                , isMsgUnfurl = Nothing+                                , messageBlocks = Nothing+                                , authorId = Nothing+                                , channelId = Nothing+                                , channelTeam = Nothing+                                , isAppUnfurl = Nothing+                                , appUnfurlUrl = Nothing+                                , fromUrl = Nothing+                                }+                            )+                        , raw = Object+                            ( fromList+                                [+                                    ( "any"+                                    , Object+                                        ( fromList+                                            [+                                                ( "specific"+                                                , String "format"+                                                )+                                            ]+                                        )+                                    )+                                ,+                                    ( "but"+                                    , String "It should still parse fine."+                                    )+                                ,+                                    ( "does"+                                    , String "not"+                                    )+                                ,+                                    ( "follow"+                                    , Number 0.0+                                    )+                                ,+                                    ( "or"+                                    , Array+                                        [ String "spec" ]+                                    )+                                ,+                                    ( "that"+                                    , String "Slack has ever published."+                                    )+                                ,+                                    ( "this"+                                    , String "attachments"+                                    )+                                ]+                            )+                        }+                    ]+                }+            )+        }+    )
+ tests/golden/SlackWebhookEvent/non_spec_attachment.json view
@@ -0,0 +1,58 @@+{+    "token": "aaaa",+    "team_id": "T043DB835ML",+    "context_team_id": "T043DB835ML",+    "context_enterprise_id": null,+    "api_app_id": "A07JRKQPZDH",+    "event": {+        "user": "U07M725KAD7",+        "type": "message",+        "ts": "1725998548.581159",+        "bot_id": "B07LDR01Z63",+        "app_id": "A01BP7R4KNY",+        "text": "",+        "team": "T043DB835ML",+        "bot_profile": {+            "id": "B07LDR01Z63",+            "deleted": false,+            "name": "GitHub",+            "updated": 1725998199,+            "app_id": "A01BP7R4KNY",+            "icons": {+                "image_36": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_36.png",+                "image_48": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_48.png",+                "image_72": "https://avatars.slack-edge.com/2020-11-25/1527503386626_319578f21381f9641cd8_72.png"+            },+            "team_id": "T043DB835ML"+        },+        "attachments": [+            {+                "this": "attachments",+                "does": "not",+                "follow": 0,+                "any": {+                    "specific": "format"+                },+                "or": ["spec"],+                "that": "Slack has ever published.",+                "but": "It should still parse fine."+            }+        ],+        "channel": "C07LRFB3C8M",+        "event_ts": "1725998548.581159",+        "channel_type": "channel"+    },+    "type": "event_callback",+    "event_id": "Ev07MH6R7FDE",+    "event_time": 1725998548,+    "authorizations": [+        {+            "enterprise_id": null,+            "team_id": "T043DB835ML",+            "user_id": "U07K4E18KJM",+            "is_bot": true,+            "is_enterprise_install": false+        }+    ],+    "is_ext_shared_channel": false+}
+ tests/golden/SlackWebhookEvent/share_with_message.golden view
@@ -0,0 +1,253 @@+EventEventCallback+    ( EventCallback+        { eventId = EventId+            { unEventId = "Ev07K61TELMR" }+        , teamId = TeamId+            { unTeamId = "T043DB835ML" }+        , eventTime = MkSystemTime+            { systemSeconds = 1724966800+            , systemNanoseconds = 0+            }+        , event = EventMessage+            ( MessageEvent+                { blocks = Just+                    [ RichText+                        { blockId = Just+                            ( NonEmptyText "Yyg6K" )+                        , elements =+                            [ RichTextSectionItemRichText+                                [ RichItemText "hello check that out!"+                                    ( RichStyle+                                        { rsBold = False+                                        , rsItalic = False+                                        }+                                    )+                                ]+                            ]+                        }+                    ]+                , channel = ConversationId+                    { unConversationId = "C07KHDGQ7K3" }+                , text = "hello check that out!"+                , channelType = Channel+                , files = Nothing+                , user = UserId+                    { unUserId = "U05JAA9EN4T" }+                , ts = "1724966800.923979"+                , threadTs = Nothing+                , appId = Nothing+                , botId = Nothing+                , attachments = Just+                    [ MessageAttachment+                        { decoded = Just+                            ( DecodedMessageAttachment+                                { fallback = Just "[August 29th, 2024 2:26 PM] lev: this will be shared with a message"+                                , color = Just "D0D0D0"+                                , pretext = Nothing+                                , authorName = Just "lev"+                                , authorLink = Just "https://myworkspace.slack.com/team/U05JAA9EN4T"+                                , authorIcon = Just "https://avatars.slack-edge.com/2023-07-22/5607926127815_66b9d49327ec16a887e5_48.png"+                                , title = Nothing+                                , titleLink = Nothing+                                , text = Just "this will be shared with a message"+                                , fields = Nothing+                                , imageUrl = Nothing+                                , thumbUrl = Nothing+                                , footer = Just "Slack Conversation"+                                , footerIcon = Nothing+                                , ts = Just "1724966776.474889"+                                , isMsgUnfurl = Just True+                                , messageBlocks = Just+                                    [ AttachmentMessageBlock+                                        { team = TeamId+                                            { unTeamId = "T043DB835ML" }+                                        , channel = ConversationId+                                            { unConversationId = "C07KTH1T4CQ" }+                                        , ts = "1724966776.474889"+                                        , message = AttachmentMessageBlockMessage+                                            { blocks =+                                                [ RichText+                                                    { blockId = Just+                                                        ( NonEmptyText "BXn1o" )+                                                    , elements =+                                                        [ RichTextSectionItemRichText+                                                            [ RichItemText "this will be shared with a message"+                                                                ( RichStyle+                                                                    { rsBold = False+                                                                    , rsItalic = False+                                                                    }+                                                                )+                                                            ]+                                                        ]+                                                    }+                                                ]+                                            }+                                        }+                                    ]+                                , authorId = Just+                                    ( UserId+                                        { unUserId = "U05JAA9EN4T" }+                                    )+                                , channelId = Just+                                    ( ConversationId+                                        { unConversationId = "C07KTH1T4CQ" }+                                    )+                                , channelTeam = Just+                                    ( TeamId+                                        { unTeamId = "T043DB835ML" }+                                    )+                                , isAppUnfurl = Nothing+                                , appUnfurlUrl = Nothing+                                , fromUrl = Just "https://myworkspace.slack.com/archives/C07KTH1T4CQ/p1724966776474889"+                                }+                            )+                        , raw = Object+                            ( fromList+                                [+                                    ( "author_icon"+                                    , String "https://avatars.slack-edge.com/2023-07-22/5607926127815_66b9d49327ec16a887e5_48.png"+                                    )+                                ,+                                    ( "author_id"+                                    , String "U05JAA9EN4T"+                                    )+                                ,+                                    ( "author_link"+                                    , String "https://myworkspace.slack.com/team/U05JAA9EN4T"+                                    )+                                ,+                                    ( "author_name"+                                    , String "lev"+                                    )+                                ,+                                    ( "author_subname"+                                    , String "lev"+                                    )+                                ,+                                    ( "channel_id"+                                    , String "C07KTH1T4CQ"+                                    )+                                ,+                                    ( "channel_team"+                                    , String "T043DB835ML"+                                    )+                                ,+                                    ( "color"+                                    , String "D0D0D0"+                                    )+                                ,+                                    ( "fallback"+                                    , String "[August 29th, 2024 2:26 PM] lev: this will be shared with a message"+                                    )+                                ,+                                    ( "footer"+                                    , String "Slack Conversation"+                                    )+                                ,+                                    ( "from_url"+                                    , String "https://myworkspace.slack.com/archives/C07KTH1T4CQ/p1724966776474889"+                                    )+                                ,+                                    ( "is_msg_unfurl"+                                    , Bool True+                                    )+                                ,+                                    ( "is_share"+                                    , Bool True+                                    )+                                ,+                                    ( "message_blocks"+                                    , Array+                                        [ Object+                                            ( fromList+                                                [+                                                    ( "channel"+                                                    , String "C07KTH1T4CQ"+                                                    )+                                                ,+                                                    ( "message"+                                                    , Object+                                                        ( fromList+                                                            [+                                                                ( "blocks"+                                                                , Array+                                                                    [ Object+                                                                        ( fromList+                                                                            [+                                                                                ( "block_id"+                                                                                , String "BXn1o"+                                                                                )+                                                                            ,+                                                                                ( "elements"+                                                                                , Array+                                                                                    [ Object+                                                                                        ( fromList+                                                                                            [+                                                                                                ( "elements"+                                                                                                , Array+                                                                                                    [ Object+                                                                                                        ( fromList+                                                                                                            [+                                                                                                                ( "text"+                                                                                                                , String "this will be shared with a message"+                                                                                                                )+                                                                                                            ,+                                                                                                                ( "type"+                                                                                                                , String "text"+                                                                                                                )+                                                                                                            ]+                                                                                                        )+                                                                                                    ]+                                                                                                )+                                                                                            ,+                                                                                                ( "type"+                                                                                                , String "rich_text_section"+                                                                                                )+                                                                                            ]+                                                                                        )+                                                                                    ]+                                                                                )+                                                                            ,+                                                                                ( "type"+                                                                                , String "rich_text"+                                                                                )+                                                                            ]+                                                                        )+                                                                    ]+                                                                )+                                                            ]+                                                        )+                                                    )+                                                ,+                                                    ( "team"+                                                    , String "T043DB835ML"+                                                    )+                                                ,+                                                    ( "ts"+                                                    , String "1724966776.474889"+                                                    )+                                                ]+                                            )+                                        ]+                                    )+                                ,+                                    ( "mrkdwn_in"+                                    , Array+                                        [ String "text" ]+                                    )+                                ,+                                    ( "text"+                                    , String "this will be shared with a message"+                                    )+                                ,+                                    ( "ts"+                                    , String "1724966776.474889"+                                    )+                                ]+                            )+                        }+                    ]+                }+            )+        }+    )
+ tests/golden/SlackWebhookEvent/share_with_message.json view
@@ -0,0 +1,95 @@+{+    "token": "d90zMGO2eDLrkLHJYcKevn2b",+    "team_id": "T043DB835ML",+    "context_team_id": "T043DB835ML",+    "context_enterprise_id": null,+    "api_app_id": "A07JRKQPZDH",+    "event": {+        "user": "U05JAA9EN4T",+        "type": "message",+        "ts": "1724966800.923979",+        "text": "hello check that out!",+        "team": "T043DB835ML",+        "attachments": [+            {+                "ts": "1724966776.474889",+                "author_id": "U05JAA9EN4T",+                "channel_id": "C07KTH1T4CQ",+                "channel_team": "T043DB835ML",+                "is_msg_unfurl": true,+                "message_blocks": [+                    {+                        "team": "T043DB835ML",+                        "channel": "C07KTH1T4CQ",+                        "ts": "1724966776.474889",+                        "message": {+                            "blocks": [+                                {+                                    "type": "rich_text",+                                    "block_id": "BXn1o",+                                    "elements": [+                                        {+                                            "type": "rich_text_section",+                                            "elements": [+                                                {+                                                    "type": "text",+                                                    "text": "this will be shared with a message"+                                                }+                                            ]+                                        }+                                    ]+                                }+                            ]+                        }+                    }+                ],+                "color": "D0D0D0",+                "from_url": "https://myworkspace.slack.com/archives/C07KTH1T4CQ/p1724966776474889",+                "is_share": true,+                "fallback": "[August 29th, 2024 2:26 PM] lev: this will be shared with a message",+                "text": "this will be shared with a message",+                "author_name": "lev",+                "author_link": "https://myworkspace.slack.com/team/U05JAA9EN4T",+                "author_icon": "https://avatars.slack-edge.com/2023-07-22/5607926127815_66b9d49327ec16a887e5_48.png",+                "author_subname": "lev",+                "mrkdwn_in": [+                    "text"+                ],+                "footer": "Slack Conversation"+            }+        ],+        "blocks": [+            {+                "type": "rich_text",+                "block_id": "Yyg6K",+                "elements": [+                    {+                        "type": "rich_text_section",+                        "elements": [+                            {+                                "type": "text",+                                "text": "hello check that out!"+                            }+                        ]+                    }+                ]+            }+        ],+        "channel": "C07KHDGQ7K3",+        "event_ts": "1724966800.923979",+        "channel_type": "channel"+    },+    "type": "event_callback",+    "event_id": "Ev07K61TELMR",+    "event_time": 1724966800,+    "authorizations": [+        {+            "enterprise_id": null,+            "team_id": "T043DB835ML",+            "user_id": "U07K4E18KJM",+            "is_bot": true,+            "is_enterprise_install": false+        }+    ],+    "is_ext_shared_channel": false+}
tests/golden/SlackWebhookEvent/share_without_message.golden view
@@ -22,6 +22,220 @@                 , threadTs = Nothing                 , appId = Nothing                 , botId = Nothing+                , attachments = Just+                    [ MessageAttachment+                        { decoded = Just+                            ( DecodedMessageAttachment+                                { fallback = Just "[October 12th, 2022 4:04 PM] jadel: blahblahblahblahblah"+                                , color = Just "D0D0D0"+                                , pretext = Nothing+                                , authorName = Just "jadel"+                                , authorLink = Just "https://jadeapptesting.slack.com/team/U043H11ES4V"+                                , authorIcon = Just "https://secure.gravatar.com/avatar/dcd5bc53dcfaca62ddc3f5726d07ba13.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-48.png"+                                , title = Nothing+                                , titleLink = Nothing+                                , text = Just "blahblahblahblahblah"+                                , fields = Nothing+                                , imageUrl = Nothing+                                , thumbUrl = Nothing+                                , footer = Just "Posted in #testing-slack-app"+                                , footerIcon = Nothing+                                , ts = Just "1665615886.364019"+                                , isMsgUnfurl = Just True+                                , messageBlocks = Just+                                    [ AttachmentMessageBlock+                                        { team = TeamId+                                            { unTeamId = "T043DB835ML" }+                                        , channel = ConversationId+                                            { unConversationId = "C043KSKGJUB" }+                                        , ts = "1665615886.364019"+                                        , message = AttachmentMessageBlockMessage+                                            { blocks =+                                                [ RichText+                                                    { blockId = Just+                                                        ( NonEmptyText "TNHa4" )+                                                    , elements =+                                                        [ RichTextSectionItemRichText+                                                            [ RichItemText "blahblahblahblahblah"+                                                                ( RichStyle+                                                                    { rsBold = False+                                                                    , rsItalic = False+                                                                    }+                                                                )+                                                            ]+                                                        ]+                                                    }+                                                ]+                                            }+                                        }+                                    ]+                                , authorId = Just+                                    ( UserId+                                        { unUserId = "U043H11ES4V" }+                                    )+                                , channelId = Just+                                    ( ConversationId+                                        { unConversationId = "C043KSKGJUB" }+                                    )+                                , channelTeam = Just+                                    ( TeamId+                                        { unTeamId = "T043DB835ML" }+                                    )+                                , isAppUnfurl = Nothing+                                , appUnfurlUrl = Nothing+                                , fromUrl = Just "https://jadeapptesting.slack.com/archives/C043KSKGJUB/p1665615886364019"+                                }+                            )+                        , raw = Object+                            ( fromList+                                [+                                    ( "author_icon"+                                    , String "https://secure.gravatar.com/avatar/dcd5bc53dcfaca62ddc3f5726d07ba13.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0019-48.png"+                                    )+                                ,+                                    ( "author_id"+                                    , String "U043H11ES4V"+                                    )+                                ,+                                    ( "author_link"+                                    , String "https://jadeapptesting.slack.com/team/U043H11ES4V"+                                    )+                                ,+                                    ( "author_name"+                                    , String "jadel"+                                    )+                                ,+                                    ( "author_subname"+                                    , String "jadel"+                                    )+                                ,+                                    ( "channel_id"+                                    , String "C043KSKGJUB"+                                    )+                                ,+                                    ( "channel_name"+                                    , String "testing-slack-app"+                                    )+                                ,+                                    ( "channel_team"+                                    , String "T043DB835ML"+                                    )+                                ,+                                    ( "color"+                                    , String "D0D0D0"+                                    )+                                ,+                                    ( "fallback"+                                    , String "[October 12th, 2022 4:04 PM] jadel: blahblahblahblahblah"+                                    )+                                ,+                                    ( "footer"+                                    , String "Posted in #testing-slack-app"+                                    )+                                ,+                                    ( "from_url"+                                    , String "https://jadeapptesting.slack.com/archives/C043KSKGJUB/p1665615886364019"+                                    )+                                ,+                                    ( "is_msg_unfurl"+                                    , Bool True+                                    )+                                ,+                                    ( "is_share"+                                    , Bool True+                                    )+                                ,+                                    ( "message_blocks"+                                    , Array+                                        [ Object+                                            ( fromList+                                                [+                                                    ( "channel"+                                                    , String "C043KSKGJUB"+                                                    )+                                                ,+                                                    ( "message"+                                                    , Object+                                                        ( fromList+                                                            [+                                                                ( "blocks"+                                                                , Array+                                                                    [ Object+                                                                        ( fromList+                                                                            [+                                                                                ( "block_id"+                                                                                , String "TNHa4"+                                                                                )+                                                                            ,+                                                                                ( "elements"+                                                                                , Array+                                                                                    [ Object+                                                                                        ( fromList+                                                                                            [+                                                                                                ( "elements"+                                                                                                , Array+                                                                                                    [ Object+                                                                                                        ( fromList+                                                                                                            [+                                                                                                                ( "text"+                                                                                                                , String "blahblahblahblahblah"+                                                                                                                )+                                                                                                            ,+                                                                                                                ( "type"+                                                                                                                , String "text"+                                                                                                                )+                                                                                                            ]+                                                                                                        )+                                                                                                    ]+                                                                                                )+                                                                                            ,+                                                                                                ( "type"+                                                                                                , String "rich_text_section"+                                                                                                )+                                                                                            ]+                                                                                        )+                                                                                    ]+                                                                                )+                                                                            ,+                                                                                ( "type"+                                                                                , String "rich_text"+                                                                                )+                                                                            ]+                                                                        )+                                                                    ]+                                                                )+                                                            ]+                                                        )+                                                    )+                                                ,+                                                    ( "team"+                                                    , String "T043DB835ML"+                                                    )+                                                ,+                                                    ( "ts"+                                                    , String "1665615886.364019"+                                                    )+                                                ]+                                            )+                                        ]+                                    )+                                ,+                                    ( "mrkdwn_in"+                                    , Array+                                        [ String "text" ]+                                    )+                                ,+                                    ( "text"+                                    , String "blahblahblahblahblah"+                                    )+                                ,+                                    ( "ts"+                                    , String "1665615886.364019"+                                    )+                                ]+                            )+                        }+                    ]                 }             )         }
tests/golden/SlackWebhookEvent/slackbotIm.golden view
@@ -46,6 +46,7 @@                 , threadTs = Nothing                 , appId = Nothing                 , botId = Nothing+                , attachments = Nothing                 }             )         }