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 +27/−0
- main/cli.hs +6/−4
- slack-web.cabal +9/−7
- src/Web/Slack.hs +25/−26
- src/Web/Slack/AesonUtils.hs +2/−2
- src/Web/Slack/Api.hs +5/−6
- src/Web/Slack/Chat.hs +9/−10
- src/Web/Slack/Classy.hs +20/−21
- src/Web/Slack/Common.hs +14/−15
- src/Web/Slack/Conversation.hs +21/−22
- src/Web/Slack/Experimental/Blocks.hs +101/−79
- src/Web/Slack/Experimental/Blocks/Types.hs +86/−36
- src/Web/Slack/Experimental/Events/Types.hs +153/−0
- src/Web/Slack/Experimental/RequestVerification.hs +21/−20
- src/Web/Slack/Internal.hs +1/−1
- src/Web/Slack/MessageParser.hs +5/−6
- src/Web/Slack/Pager.hs +7/−8
- src/Web/Slack/Prelude.hs +10/−11
- src/Web/Slack/Types.hs +14/−15
- src/Web/Slack/User.hs +8/−9
- src/Web/Slack/UsersConversations.hs +6/−7
- src/Web/Slack/Util.hs +6/−7
- tests/JSONGolden.hs +1/−1
- tests/TestImport.hs +11/−12
- tests/TestImport/Aeson.hs +4/−2
- tests/Web/Slack/ConversationSpec.hs +3/−4
- tests/Web/Slack/Experimental/Blocks/TypesSpec.hs +162/−0
- tests/Web/Slack/Experimental/BlocksSpec.hs +35/−6
- tests/Web/Slack/Experimental/Events/TypesSpec.hs +8/−0
- tests/Web/Slack/MessageParserSpec.hs +40/−30
- tests/Web/Slack/PagerSpec.hs +26/−23
- tests/golden/BlockKitBuilderMessage/simple_blocks.golden.json +25/−0
- tests/golden/SlackWebhookEvent/botMessage.golden +1/−0
- tests/golden/SlackWebhookEvent/circleci.golden +57/−0
- tests/golden/SlackWebhookEvent/circleci.json +119/−0
- tests/golden/SlackWebhookEvent/email_message.golden +1/−0
- tests/golden/SlackWebhookEvent/forwarded_message.golden +238/−0
- tests/golden/SlackWebhookEvent/forwarded_message.json +78/−0
- tests/golden/SlackWebhookEvent/github_notification.golden +197/−0
- tests/golden/SlackWebhookEvent/github_notification.json +88/−0
- tests/golden/SlackWebhookEvent/github_notification_ts_string.golden +197/−0
- tests/golden/SlackWebhookEvent/github_notification_ts_string.json +88/−0
- tests/golden/SlackWebhookEvent/github_with_link.golden +145/−0
- tests/golden/SlackWebhookEvent/github_with_link.json +72/−0
- tests/golden/SlackWebhookEvent/link.golden +1/−0
- tests/golden/SlackWebhookEvent/messageExample.golden +1/−0
- tests/golden/SlackWebhookEvent/messageIm.golden +1/−0
- tests/golden/SlackWebhookEvent/message_file_share.golden +1/−0
- tests/golden/SlackWebhookEvent/message_file_share_slack_connect.golden +1/−0
- tests/golden/SlackWebhookEvent/message_rich_text.golden +1/−0
- tests/golden/SlackWebhookEvent/message_subtype_bot_message.golden +46/−0
- tests/golden/SlackWebhookEvent/message_subtype_bot_message.json +53/−0
- tests/golden/SlackWebhookEvent/non_spec_attachment.golden +99/−0
- tests/golden/SlackWebhookEvent/non_spec_attachment.json +58/−0
- tests/golden/SlackWebhookEvent/share_with_message.golden +253/−0
- tests/golden/SlackWebhookEvent/share_with_message.json +95/−0
- tests/golden/SlackWebhookEvent/share_without_message.golden +214/−0
- tests/golden/SlackWebhookEvent/slackbotIm.golden +1/−0
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> 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> 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>b.\n:slightly_smiling_face:" `shouldBe` "a:<br/><blockquote>b.</blockquote>:>slightly_smiling_face<:"- it "properly parses multiline blockquotes" $- msgToHtml "> first row\n> second row\nthird row\n> 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>b.\n:slightly_smiling_face:"+ `shouldBe` "a:<br/><blockquote>b.</blockquote>:>slightly_smiling_face<:"+ it "properly parses multiline blockquotes"+ $ msgToHtml "> first row\n> second row\nthird row\n> 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;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&amp;quot;}"+ )+ ]+ )+ , Object+ ( fromList+ [+ ( "id"+ , String "2"+ )+ ,+ ( "name"+ , String "edit"+ )+ ,+ ( "style"+ , String ""+ )+ ,+ ( "text"+ , String "Edit"+ )+ ,+ ( "type"+ , String "button"+ )+ ,+ ( "value"+ , String "{&amp;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&amp;quot;}"+ )+ ]+ )+ , Object+ ( fromList+ [+ ( "id"+ , String "3"+ )+ ,+ ( "name"+ , String "close"+ )+ ,+ ( "style"+ , String "danger"+ )+ ,+ ( "text"+ , String "Close"+ )+ ,+ ( "type"+ , String "button"+ )+ ,+ ( "value"+ , String "{&amp;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&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;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&amp;quot;}",+ "style": ""+ },+ {+ "id": "2",+ "name": "edit",+ "text": "Edit",+ "type": "button",+ "value": "{&amp;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&amp;quot;}",+ "style": ""+ },+ {+ "id": "3",+ "name": "close",+ "text": "Close",+ "type": "button",+ "value": "{&amp;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&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;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&amp;quot;}"+ )+ ]+ )+ , Object+ ( fromList+ [+ ( "id"+ , String "2"+ )+ ,+ ( "name"+ , String "edit"+ )+ ,+ ( "style"+ , String ""+ )+ ,+ ( "text"+ , String "Edit"+ )+ ,+ ( "type"+ , String "button"+ )+ ,+ ( "value"+ , String "{&amp;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&amp;quot;}"+ )+ ]+ )+ , Object+ ( fromList+ [+ ( "id"+ , String "3"+ )+ ,+ ( "name"+ , String "close"+ )+ ,+ ( "style"+ , String "danger"+ )+ ,+ ( "text"+ , String "Close"+ )+ ,+ ( "type"+ , String "button"+ )+ ,+ ( "value"+ , String "{&amp;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&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;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&amp;quot;}",+ "style": ""+ },+ {+ "id": "2",+ "name": "edit",+ "text": "Edit",+ "type": "button",+ "value": "{&amp;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&amp;quot;}",+ "style": ""+ },+ {+ "id": "3",+ "name": "close",+ "text": "Close",+ "type": "button",+ "value": "{&amp;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:809927288,&amp;quot;issueNumber&amp;quot;:2,&amp;quot;issueHtmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/issues/2&amp;quot;,&amp;quot;issueTitle&amp;quot;:&amp;quot;test issue 2&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;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:669978765,&amp;quot;number&amp;quot;:8,&amp;quot;htmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/pull/8&amp;quot;,&amp;quot;title&amp;quot;:&amp;quot;https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249&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;quot;selectedOrg&amp;quot;:&amp;quot;myorg&amp;quot;,&amp;quot;selectedOrgId&amp;quot;:140364112,&amp;quot;selectedRepo&amp;quot;:&amp;quot;myrepo&amp;quot;,&amp;quot;selectedRepoId&amp;quot;:669978765,&amp;quot;number&amp;quot;:8,&amp;quot;htmlUrl&amp;quot;:&amp;quot;https://github.com/myorg/myrepo/pull/8&amp;quot;,&amp;quot;title&amp;quot;:&amp;quot;https://myteam.slack.com/archives/C07KTH1T4CQ/p1730339894629249&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 } ) }
@@ -52,6 +52,7 @@ , threadTs = Nothing , appId = Nothing , botId = Nothing+ , attachments = Nothing } ) }
@@ -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+}
@@ -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"+ )+ ]+ )+ }+ ]+ }+ )+ }+ )
@@ -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+}
@@ -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 } ) }