packages feed

slack-web 2.1.0.0 → 2.2.0.0

raw patch · 12 files changed

+408/−7 lines, 12 filesdep ~http-api-data

Dependency ranges changed: http-api-data

Files

CHANGELOG.md view
@@ -1,3 +1,11 @@+# 2.1.1.0+* [#145](https://github.com/MercuryTechnologies/slack-web/pull/145)+  Implement `conversations.info` API method.++  Breaking change: imIsUserDeleted is now Maybe to reflect reality.+* [#146](https://github.com/MercuryTechnologies/slack-web/pull/146)+  Implement `conversations.join` API method.+ # 2.1.0.0 (2025-03-06) * [#138](https://github.com/MercuryTechnologies/slack-web/pull/138)   Implement `views.publish` method and App Home tab events.
slack-web.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: slack-web-version: 2.1.0.0+version: 2.2.0.0  build-type: Simple @@ -35,6 +35,10 @@   tests/golden/PublishResp/*.golden   tests/golden/ResponseJSON/*.json   tests/golden/ResponseJSON/*.golden+  tests/golden/InfoRsp/*.golden+  tests/golden/InfoRsp/*.json+  tests/golden/JoinRsp/*.json+  tests/golden/JoinRsp/*.golden  category: Web 
src/Web/Slack/Conversation.hs view
@@ -5,6 +5,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE StrictData #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeOperators #-} -- FIXME: squashes warnings {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} @@ -34,6 +35,15 @@   RepliesReq (..),   mkRepliesReq,   ResponseMetadata (..),+  Api,+  InfoReq (..),+  InfoRsp (..),+  conversationsInfo,+  conversationsInfo_,+  JoinReq (..),+  JoinRsp (..),+  conversationsJoin,+  conversationsJoin_, ) where  import Data.Aeson@@ -43,9 +53,14 @@ import Data.Aeson.Types import Data.Scientific import Data.Text qualified as T+import Servant.API (AuthProtect, FormUrlEncoded, JSON, Post, ReqBody, (:<|>) (..), (:>))+import Servant.Client (ClientM, client)+import Servant.Client.Core (AuthenticatedRequest) import Web.FormUrlEncoded import Web.HttpApiData import Web.Slack.Common+import Web.Slack.Internal (ResponseJSON (..), SlackConfig (..), mkSlackAuthenticateReq, run)+import Web.Slack.Pager (Response) import Web.Slack.Pager.Types (PagedRequest (..), PagedResponse (..), ResponseMetadata (..)) import Web.Slack.Prelude import Web.Slack.Util@@ -118,7 +133,7 @@ $(deriveJSON (jsonOpts "channel") ''ChannelConversation)  -- | Conversation object representing a private channel or---   _a multi-party instant message (mpim)*, which only invited people in the+--   _a multi-party instant message (mpim)_, which only invited people in the --  team can join in and see. data GroupConversation = GroupConversation   { groupId :: ConversationId@@ -171,7 +186,7 @@   , imIsArchived :: Bool   , imIsOrgShared :: Bool   , imUser :: UserId-  , imIsUserDeleted :: Bool+  , imIsUserDeleted :: Maybe Bool   , imPriority :: Scientific   }   deriving stock (Eq, Show, Generic)@@ -429,3 +444,143 @@     , repliesReqOldest = Nothing     , repliesReqInclusive = True     }++-- | @conversations.info@ request: retrieve a conversation's metadata.+--+-- <https://api.slack.com/methods/conversations.info>+--+-- @since 2.2.0.0+data InfoReq = InfoReq+  { infoReqChannel :: ConversationId+  , infoReqIncludeLocale :: Maybe Bool+  , infoReqIncludeNumMembers :: Maybe Bool+  }+  deriving stock (Eq, Show, Generic)++instance ToForm InfoReq where+  toForm InfoReq {..} =+    [("channel", unConversationId infoReqChannel)]+      <> toQueryParamIfJust "include_locale" infoReqIncludeLocale+      <> toQueryParamIfJust "include_num_members" infoReqIncludeNumMembers++-- | @conversations.info@ response+--+-- <https://api.slack.com/methods/conversations.info>+--+-- @since 2.2.0.0+data InfoRsp = InfoRsp+  { infoRspChannel :: Conversation+  }+  deriving stock (Eq, Show, Generic)++$(deriveJSON (jsonOpts "infoRsp") ''InfoRsp)++-- | FIXME(jadel): move the rest of the Conversations API into here since the old "shoving all the API in one spot" is soft deprecated.+-- @since 2.2.0.0+type Api =+  "conversations.info"+    :> AuthProtect "token"+    :> ReqBody '[FormUrlEncoded] InfoReq+    :> Post '[JSON] (ResponseJSON InfoRsp)+    :<|> "conversations.join"+      :> AuthProtect "token"+      :> ReqBody '[FormUrlEncoded] JoinReq+      :> Post '[JSON] (ResponseJSON JoinRsp)++-- | Joins a conversation.+--+-- <https://api.slack.com/methods/conversations.join>+data JoinReq = JoinReq+  { joinReqChannel :: ConversationId+  }+  deriving stock (Eq, Show, Generic)++instance ToForm JoinReq where+  toForm = genericToForm (formOpts "joinReq")++-- |+-- @+-- {+--     "ok": true,+--     "channel": {+--         "id": "C061EG9SL",+--         "name": "general",+--         "is_channel": true,+--         "is_group": false,+--         "is_im": false,+--         "created": 1449252889,+--         "creator": "U061F7AUR",+--         "is_archived": false,+--         "is_general": true,+--         "unlinked": 0,+--         "name_normalized": "general",+--         "is_shared": false,+--         "is_ext_shared": false,+--         "is_org_shared": false,+--         "pending_shared": [],+--         "is_pending_ext_shared": false,+--         "is_member": true,+--         "is_private": false,+--         "is_mpim": false,+--         "topic": {+--             "value": "Which widget do you worry about?",+--             "creator": "",+--             "last_set": 0+--         },+--         "purpose": {+--             "value": "For widget discussion",+--             "creator": "",+--             "last_set": 0+--         },+--         "previous_names": []+--     },+--     "warning": "already_in_channel",+--     "response_metadata": {+--         "warnings": [+--             "already_in_channel"+--         ]+--     }+-- }+-- @+data JoinRsp = JoinRsp+  { joinRspChannel :: Conversation+  }+  deriving stock (Show)++$(deriveFromJSON (jsonOpts "joinRsp") ''JoinRsp)++-- | @since 2.2.0.0+conversationsJoin_ :: AuthenticatedRequest (AuthProtect "token") -> JoinReq -> ClientM (ResponseJSON JoinRsp)++-- | @since 2.2.0.0+conversationsInfo_ ::+  AuthenticatedRequest (AuthProtect "token") ->+  InfoReq ->+  ClientM (ResponseJSON InfoRsp)+conversationsInfo_ :<|> conversationsJoin_ = client (Proxy @Api)++-- | Retrieve a conversation's metadata.+--+-- <https://api.slack.com/methods/conversations.info>+--+-- @since 2.2.0.0+conversationsInfo ::+  SlackConfig ->+  InfoReq ->+  IO (Response InfoRsp)+conversationsInfo = flip $ \listReq -> do+  authR <- mkSlackAuthenticateReq+  run (conversationsInfo_ authR listReq) . slackConfigManager++-- | Joins a conversation.+--+-- <https://api.slack.com/methods/conversations.join>+--+-- @since 2.2.0.0+conversationsJoin ::+  SlackConfig ->+  JoinReq ->+  IO (Response JoinRsp)+conversationsJoin slackConfig req = do+  let authR = mkSlackAuthenticateReq slackConfig+  run (conversationsJoin_ authR req) . slackConfigManager $ slackConfig
src/Web/Slack/Internal.hs view
@@ -23,6 +23,8 @@ newtype ResponseJSON a = ResponseJSON (Either Common.ResponseSlackError a)   deriving stock (Show) +type role ResponseJSON representational+ instance (FromJSON a) => FromJSON (ResponseJSON a) where   parseJSON = withObject "Response" $ \o -> do     ok <- o .: "ok"
src/Web/Slack/Pager.hs view
@@ -6,8 +6,8 @@   module Web.Slack.Pager.Types, ) where +import Data.Kind (Type) import Web.Slack.Common qualified as Common-import Web.Slack.Conversation qualified as Conversation import Web.Slack.Pager.Types import Web.Slack.Prelude @@ -19,6 +19,8 @@ --   If there is no more response, the action returns an empty list. type LoadPage m a = m (Response [a]) +type LoadPage :: forall {k}. (Type -> k) -> Type -> k+ -- | Utility function for 'LoadPage'. Perform the 'LoadPage' action to call --   the function with the loaded page, until an empty page is loaded. loadingPage :: (Monad m, Monoid n) => LoadPage m a -> (Response [a] -> m n) -> m n@@ -46,7 +48,7 @@    let requestFromCursor cursor = setCursor cursor initialRequest       collectAndUpdateCursor resp = do-        let newCursor = Conversation.responseMetadataNextCursor =<< getResponseMetadata resp+        let newCursor = responseMetadataNextCursor =<< getResponseMetadata resp             -- emptyCursor is used for the marker to show that there are no more pages.             cursorToSave = if isNothing newCursor then emptyCursor else newCursor         writeIORef cursorRef cursorToSave
tests/Web/Slack/ConversationSpec.hs view
@@ -114,6 +114,8 @@    describe "Golden tests" $ do     mapM_ (oneGoldenTestDecode @Conversation) ["shared_channel"]+    mapM_ (oneGoldenTestDecode @InfoRsp) ["general"]+    mapM_ (oneGoldenTestDecode @JoinRsp) ["test"]    it "errors accurately if no variant matches" $ do     let badData =
+ tests/golden/InfoRsp/general.golden view
@@ -0,0 +1,37 @@+InfoRsp+    { infoRspChannel = Channel+        ( ChannelConversation+            { channelId = ConversationId+                { unConversationId = "C043YJGBY49" }+            , channelName = "general"+            , channelCreated = 1663890553+            , channelIsArchived = False+            , channelIsGeneral = True+            , channelUnlinked = 0+            , channelNameNormalized = "general"+            , channelIsShared = False+            , channelCreator = UserId+                { unUserId = "U043H11ES4V" }+            , channelIsExtShared = False+            , channelIsOrgShared = False+            , channelSharedTeamIds = Just+                [ TeamId+                    { unTeamId = "T043DB835ML" }+                ]+            , channelIsPendingExtShared = False+            , channelIsMember = Just True+            , channelTopic = Topic+                { topicValue = ""+                , topicCreator = ""+                , topicLastSet = 0+                }+            , channelPurpose = Purpose+                { purposeValue = "This is the one channel that will always include everyone. It’s a great spot for announcements and team-wide conversations."+                , purposeCreator = "U043H11ES4V"+                , purposeLastSet = 1663890553+                }+            , channelPreviousNames = []+            , channelNumMembers = Nothing+            }+        )+    }
+ tests/golden/InfoRsp/general.json view
@@ -0,0 +1,49 @@+{+    "ok": true,+    "channel": {+        "id": "C043YJGBY49",+        "name": "general",+        "is_channel": true,+        "is_group": false,+        "is_im": false,+        "is_mpim": false,+        "is_private": false,+        "created": 1663890553,+        "is_archived": false,+        "is_general": true,+        "unlinked": 0,+        "name_normalized": "general",+        "is_shared": false,+        "is_org_shared": false,+        "is_pending_ext_shared": false,+        "pending_shared": [],+        "context_team_id": "T043DB835ML",+        "updated": 1740098274213,+        "parent_conversation": null,+        "creator": "U043H11ES4V",+        "is_read_only": false,+        "is_thread_only": false,+        "is_non_threadable": false,+        "is_ext_shared": false,+        "shared_team_ids": [+            "T043DB835ML"+        ],+        "pending_connected_team_ids": [],+        "is_member": true,+        "last_read": "1740098745.617639",+        "topic": {+            "value": "",+            "creator": "",+            "last_set": 0+        },+        "purpose": {+            "value": "This is the one channel that will always include everyone. It’s a great spot for announcements and team-wide conversations.",+            "creator": "U043H11ES4V",+            "last_set": 1663890553+        },+        "properties": {+            "use_case": "welcome"+        },+        "previous_names": []+    }+}
+ tests/golden/InfoRsp/im.json view
@@ -0,0 +1,71 @@+{+    "ok": true,+    "channel": {+        "id": "D0442US94JD",+        "created": 1663960005,+        "is_archived": false,+        "is_im": true,+        "is_org_shared": false,+        "context_team_id": "T043DB835ML",+        "updated": 1740098832769,+        "user": "U043H11ES4V",+        "last_read": "1671145573.165309",+        "latest": {+            "user": "U0442US8QGH",+            "type": "message",+            "ts": "1741206064.642559",+            "bot_id": "B0439P161B9",+            "app_id": "A0442TUPHGR",+            "text": "Done!",+            "team": "T043DB835ML",+            "bot_profile": {+                "id": "B0439P161B9",+                "deleted": false,+                "name": "Slacklinker dev",+                "updated": 1740097237,+                "app_id": "A0442TUPHGR",+                "icons": {+                    "image_36": "https://a.slack-edge.com/80588/img/plugins/app/bot_36.png",+                    "image_48": "https://a.slack-edge.com/80588/img/plugins/app/bot_48.png",+                    "image_72": "https://a.slack-edge.com/80588/img/plugins/app/service_72.png"+                },+                "team_id": "T043DB835ML"+            },+            "blocks": [+                {+                    "type": "rich_text",+                    "block_id": "rdhT",+                    "elements": [+                        {+                            "type": "rich_text_section",+                            "elements": [+                                {+                                    "type": "text",+                                    "text": "Done!"+                                }+                            ]+                        }+                    ]+                }+            ]+        },+        "unread_count": 48,+        "unread_count_display": 4,+        "is_open": true,+        "properties": {+            "tabs": [+                {+                    "type": "files",+                    "label": "",+                    "id": "files"+                }+            ],+            "tabz": [+                {+                    "type": "files"+                }+            ]+        },+        "priority": 0+    }+}
+ tests/golden/JoinRsp/test.golden view
@@ -0,0 +1,37 @@+JoinRsp+    { joinRspChannel = Channel+        ( ChannelConversation+            { channelId = ConversationId+                { unConversationId = "C0451SKQN72" }+            , channelName = "new-channel-2"+            , channelCreated = 1664407230+            , channelIsArchived = False+            , channelIsGeneral = False+            , channelUnlinked = 0+            , channelNameNormalized = "new-channel-2"+            , channelIsShared = False+            , channelCreator = UserId+                { unUserId = "U043H11ES4V" }+            , channelIsExtShared = False+            , channelIsOrgShared = False+            , channelSharedTeamIds = Just+                [ TeamId+                    { unTeamId = "T043DB835ML" }+                ]+            , channelIsPendingExtShared = False+            , channelIsMember = Just True+            , channelTopic = Topic+                { topicValue = ""+                , topicCreator = ""+                , topicLastSet = 0+                }+            , channelPurpose = Purpose+                { purposeValue = ""+                , purposeCreator = ""+                , purposeLastSet = 0+                }+            , channelPreviousNames = []+            , channelNumMembers = Nothing+            }+        )+    }
+ tests/golden/JoinRsp/test.json view
@@ -0,0 +1,34 @@+{+  "ok": true,+  "channel": {+    "id": "C0451SKQN72",+    "name": "new-channel-2",+    "is_channel": true,+    "is_group": false,+    "is_im": false,+    "is_mpim": false,+    "is_private": false,+    "created": 1664407230,+    "is_archived": false,+    "is_general": false,+    "unlinked": 0,+    "name_normalized": "new-channel-2",+    "is_shared": false,+    "is_org_shared": false,+    "is_pending_ext_shared": false,+    "pending_shared": [],+    "context_team_id": "T043DB835ML",+    "parent_conversation": null,+    "creator": "U043H11ES4V",+    "is_ext_shared": false,+    "shared_team_ids": ["T043DB835ML"],+    "pending_connected_team_ids": [],+    "is_member": true,+    "last_read": "1664407230.583859",+    "topic": { "value": "", "creator": "", "last_set": 0 },+    "purpose": { "value": "", "creator": "", "last_set": 0 },+    "previous_names": [],+    "priority": 0+  }+}+
tests/golden/UsersConversationsResponse/im_and_channels.golden view
@@ -79,7 +79,7 @@                 , imIsOrgShared = False                 , imUser = UserId                     { unUserId = "U043H11ES4V" }-                , imIsUserDeleted = False+                , imIsUserDeleted = Just False                 , imPriority = 0.0                 }             )@@ -92,7 +92,7 @@                 , imIsOrgShared = False                 , imUser = UserId                     { unUserId = "USLACKBOT" }-                , imIsUserDeleted = False+                , imIsUserDeleted = Just False                 , imPriority = 0.0                 }             )