Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse file share events #120

Merged
merged 5 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,10 @@
# 1.6.0.0 (2022-12-14)

* [#120](https://github.com/MercuryTechnologies/slack-web/pull/120)
Add file share event parsing support.

Increase bounds on pretty-simple to 4.1 to mitigate a bug added in 4.0.

# 1.5.0.1

* [#119](https://github.com/MercuryTechnologies/slack-web/pull/119) Fix a typo
Expand Down
8 changes: 8 additions & 0 deletions flake.nix
Expand Up @@ -116,6 +116,14 @@
# 0.6.3 in the repo
refined = hfinal.refined_0_7;

pretty-simple = hfinal.callHackageDirect
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woah TIL about callHackageDirect, I've been updating all-cabal-hashes like a dummy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can't be bothered to deal with that on slack-web, so I don't. XD

all-cabal-hashes updating is a much better strategy if you have a lot of packages.

{
pkg = "pretty-simple";
ver = "4.1.2.0";
sha256 = "sha256-uM1oyi/isWMicKPIw0KKeRJzY8Zu5yQNE0A2mpeBPHg=";
}
{ };

# it's not yet in hackage2nix
string-variants = hfinal.callHackageDirect
{
Expand Down
8 changes: 6 additions & 2 deletions slack-web.cabal
Expand Up @@ -27,6 +27,8 @@ extra-source-files:
tests/golden/UpdateRsp/*.json
tests/golden/UpdateRsp/*.golden
tests/golden/BlockKitBuilderMessage/*.golden.json
tests/golden/FileObject/*.json
tests/golden/FileObject/*.golden

category: Web

Expand Down Expand Up @@ -113,6 +115,7 @@ library
Web.Slack.Classy
Web.Slack.Common
Web.Slack.Conversation
Web.Slack.Files.Types
Web.Slack.Internal
Web.Slack.MessageParser
Web.Slack.Pager
Expand Down Expand Up @@ -178,6 +181,7 @@ test-suite tests
Web.Slack.MessageParserSpec
Web.Slack.ConversationSpec
Web.Slack.ChatSpec
Web.Slack.Files.TypesSpec
Web.Slack.UsersConversationsSpec
Web.Slack.Experimental.RequestVerificationSpec
Web.Slack.Experimental.Events.TypesSpec
Expand All @@ -197,7 +201,7 @@ test-suite tests
, hspec-core
, hspec-golden
, mtl
, pretty-simple
, pretty-simple ^>= 4.1
, quickcheck-instances
, slack-web
, string-conversions
Expand Down Expand Up @@ -231,7 +235,7 @@ executable slack-web-cli
, bytestring
, monad-loops
, mtl
, pretty-simple
, pretty-simple ^>= 4.1
, text
, time
, servant-client-core
Expand Down
10 changes: 10 additions & 0 deletions src/Web/Slack/AesonUtils.hs
Expand Up @@ -5,6 +5,7 @@ import Data.Aeson qualified as J
import Data.Aeson.Types (Pair)
import Data.Char qualified as Char
import Data.Text qualified as T
import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
import Web.FormUrlEncoded qualified as F
import Web.Slack.Prelude

Expand Down Expand Up @@ -112,3 +113,12 @@ snakeCaseFormOptions =
F.defaultFormOptions
{ F.fieldLabelModifier = camelTo2 '_'
}

newtype UnixTimestamp = UnixTimestamp {unUnixTimestamp :: UTCTime}
deriving newtype (Show, Eq)

instance FromJSON UnixTimestamp where
parseJSON a = UnixTimestamp . posixSecondsToUTCTime <$> parseJSON a

instance ToJSON UnixTimestamp where
toJSON (UnixTimestamp a) = toJSON (utcTimeToPOSIXSeconds a)
11 changes: 10 additions & 1 deletion src/Web/Slack/Experimental/Events/Types.hs
Expand Up @@ -11,6 +11,7 @@ import Data.Aeson.TH
import Data.Time.Clock.System (SystemTime)
import Web.Slack.AesonUtils
import Web.Slack.Experimental.Blocks (SlackBlock)
import Web.Slack.Files.Types (FileObject)
import Web.Slack.Prelude
import Web.Slack.Types (ConversationId, TeamId, UserId)

Expand All @@ -22,11 +23,15 @@ data ChannelType = Channel | Group | Im
$(deriveJSON snakeCaseOptions ''ChannelType)

-- | <https://api.slack.com/events/message>
-- and
-- <https://api.slack.com/events/message/file_share>
data MessageEvent = MessageEvent
{ blocks :: Maybe [SlackBlock]
, channel :: ConversationId
, text :: Text
, channelType :: ChannelType
, files :: Maybe [FileObject]
-- ^ @since 1.6.0.0
, -- FIXME(jadel): clientMsgId??
user :: UserId
, ts :: Text
Expand All @@ -46,6 +51,8 @@ data MessageEvent = MessageEvent
}
deriving stock (Show)

$(deriveFromJSON snakeCaseOptions ''MessageEvent)

-- | <https://api.slack.com/events/message/message_changed>
--
-- FIXME(jadel): implement. This is mega cursed! in the normal message event
Expand All @@ -58,7 +65,6 @@ data MessageUpdateEvent = MessageUpdateEvent
}
deriving stock (Show)

$(deriveFromJSON snakeCaseOptions ''MessageEvent)
$(deriveFromJSON snakeCaseOptions ''MessageUpdateEvent)

-- | FIXME: this might be a Channel, but may also be missing some fields/have bonus
Expand Down Expand Up @@ -138,6 +144,9 @@ instance FromJSON Event where
("message", Nothing) -> EventMessage <$> parseJSON @MessageEvent (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
-- a bonus files field
("message", Just "file_share") -> EventMessage <$> parseJSON @MessageEvent (Object obj)
("channel_created", Nothing) -> EventChannelCreated <$> parseJSON (Object obj)
("channel_left", Nothing) -> EventChannelLeft <$> parseJSON (Object obj)
_ -> pure $ EventUnknown (Object obj)
Expand Down
64 changes: 64 additions & 0 deletions src/Web/Slack/Files/Types.hs
@@ -0,0 +1,64 @@
{-# LANGUAGE TemplateHaskell #-}

-- | Type definitions for the Slack files APIs.
-- See <https://api.slack.com/messaging/files>.
--
-- @since 1.6.0.0
module Web.Slack.Files.Types where

import Control.Monad.Fail (MonadFail (..))
import Data.Aeson ((.!=))
import Data.Aeson qualified as A
import Data.Aeson.KeyMap qualified as KM
import Web.Slack.AesonUtils (UnixTimestamp, snakeCaseOptions)
import Web.Slack.Prelude

-- | ID for a file, which looks something like @F2147483862@.
newtype FileId = FileId {unFileId :: Text}
deriving stock (Show, Eq)
deriving newtype (FromJSON, ToJSON)

data FileMode = Hosted | External | Snippet | Post | FileAccess
deriving stock (Show, Eq)

$(deriveJSON snakeCaseOptions ''FileMode)

-- | <https://api.slack.com/types/file>
data FileObjectVisible = FileObjectVisible
{ id :: FileId
, created :: UnixTimestamp
, name :: Text
, title :: Text
, mimetype :: Text
, urlPrivate :: Text
, isExternal :: Bool
, size :: Int
, mode :: FileMode
}
deriving stock (Show, Eq)

$(deriveJSON snakeCaseOptions ''FileObjectVisible)

data FileObject
= -- | File object is visible
VisibleFileObject FileObjectVisible
| -- | File object is in a shared channel so @files.info@ must be invoked to
-- get any further details. See
-- <https://api.slack.com/types/file#slack_connect_files> for more details.
CheckFileInfo FileId
deriving stock (Show, Eq)

instance FromJSON FileObject where
parseJSON = withObject "FileObject" $ \obj -> do
-- "visible" is undocumented, thanks Slack!
ty :: Text <- obj .:? "file_access" .!= "visible"
case ty of
"visible" -> VisibleFileObject <$> parseJSON (A.Object obj)
"check_file_info" -> CheckFileInfo <$> obj .: "id"
_ -> fail $ "unknown file_access type " <> unpack ty

instance ToJSON FileObject where
toJSON (VisibleFileObject obj) = case toJSON obj of
A.Object o -> A.Object $ o <> KM.fromList [("file_access", "visible")]
_ -> error "impossible"
toJSON (CheckFileInfo fid) = A.object [("file_access", "check_file_info"), ("id", toJSON fid)]
2 changes: 2 additions & 0 deletions tests/Web/Slack/Experimental/Events/TypesSpec.hs
Expand Up @@ -13,6 +13,8 @@ spec = describe "Types for Slack events" do
[ "messageExample"
, "messageChange"
, "message_rich_text"
, "message_file_share"
, "message_file_share_slack_connect"
, "link"
, "botMessage"
, "joinChannel"
Expand Down
15 changes: 15 additions & 0 deletions tests/Web/Slack/Files/TypesSpec.hs
@@ -0,0 +1,15 @@
module Web.Slack.Files.TypesSpec (spec) where

import JSONGolden
import TestImport
import Web.Slack.Files.Types

spec :: Spec
spec = describe "Types for Slack files" do
describe "FileObject" do
describe "FromJSON" do
mapM_
(oneGoldenTestDecode @FileObject)
[ "example"
, "real"
]
14 changes: 14 additions & 0 deletions tests/golden/FileObject/example.golden
@@ -0,0 +1,14 @@
VisibleFileObject
( FileObjectVisible
{ id = FileId
{ unFileId = "F0S43PZDF" }
, created = 2018-07-16 17:49:02 UTC
, name = "tedair.gif"
, title = "tedair.gif"
, mimetype = "image/gif"
, urlPrivate = "https://.../tedair.gif"
, isExternal = False
, size = 137531
, mode = Hosted
}
)
58 changes: 58 additions & 0 deletions tests/golden/FileObject/example.json
@@ -0,0 +1,58 @@
{
"id": "F0S43PZDF",
"created": 1531763342,
"timestamp": 1531763342,
"name": "tedair.gif",
"title": "tedair.gif",
"mimetype": "image/gif",
"filetype": "gif",
"pretty_type": "GIF",
"user": "U061F7AUR",
"editable": false,
"size": 137531,
"mode": "hosted",
"is_external": false,
"external_type": "",
"is_public": true,
"public_url_shared": false,
"display_as_bot": false,
"username": "",
"url_private": "https://.../tedair.gif",
"url_private_download": "https://.../tedair.gif",
"thumb_64": "https://.../tedair_64.png",
"thumb_80": "https://.../tedair_80.png",
"thumb_360": "https://.../tedair_360.png",
"thumb_360_w": 176,
"thumb_360_h": 226,
"thumb_160": "https://.../tedair_=_160.png",
"thumb_360_gif": "https://.../tedair_360.gif",
"image_exif_rotation": 1,
"original_w": 176,
"original_h": 226,
"deanimate_gif": "https://.../tedair_deanimate_gif.png",
"pjpeg": "https://.../tedair_pjpeg.jpg",
"permalink": "https://.../tedair.gif",
"permalink_public": "https://.../...",
"comments_count": 0,
"is_starred": false,
"shares": {
"public": {
"C0T8SE4AU": [
{
"reply_users": ["U061F7AUR"],
"reply_users_count": 1,
"reply_count": 1,
"ts": "1531763348.000001",
"thread_ts": "1531763273.000015",
"latest_reply": "1531763348.000001",
"channel_name": "file-under",
"team_id": "T061EG9R6"
}
]
}
},
"channels": ["C0T8SE4AU"],
"groups": [],
"ims": [],
"has_rich_preview": false
}
14 changes: 14 additions & 0 deletions tests/golden/FileObject/real.golden
@@ -0,0 +1,14 @@
VisibleFileObject
( FileObjectVisible
{ id = FileId
{ unFileId = "F04EEHX1EES" }
, created = 2022-12-10 00:18:47 UTC
, name = "namenamename.txt"
, title = "namenamename.txt"
, mimetype = "text/plain"
, urlPrivate = "https://files.slack.com/files-pri/T043DB835ML-F04EEHX1EES/namenamename.txt"
, isExternal = False
, size = 12
, mode = Snippet
}
)
33 changes: 33 additions & 0 deletions tests/golden/FileObject/real.json
@@ -0,0 +1,33 @@
{
"id": "F04EEHX1EES",
"created": 1670631527,
"timestamp": 1670631527,
"name": "namenamename.txt",
"title": "namenamename.txt",
"mimetype": "text/plain",
"filetype": "text",
"pretty_type": "Plain Text",
"user": "U043H11ES4V",
"user_team": "T043DB835ML",
"editable": true,
"size": 12,
"mode": "snippet",
"is_external": false,
"external_type": "",
"is_public": false,
"public_url_shared": false,
"display_as_bot": false,
"username": "",
"url_private": "https://files.slack.com/files-pri/T043DB835ML-F04EEHX1EES/namenamename.txt",
"url_private_download": "https://files.slack.com/files-pri/T043DB835ML-F04EEHX1EES/download/namenamename.txt",
"permalink": "https://jadeapptesting.slack.com/files/U043H11ES4V/F04EEHX1EES/namenamename.txt",
"permalink_public": "https://slack-files.com/T043DB835ML-F04EEHX1EES-0ce8936320",
"edit_link": "https://jadeapptesting.slack.com/files/U043H11ES4V/F04EEHX1EES/namenamename.txt/edit",
"preview": "hug a kitten",
"preview_highlight": "<div class=\"CodeMirror cm-s-default CodeMirrorServer\" oncopy=\"if(event.clipboardData){event.clipboardData.setData('text/plain',window.getSelection().toString().replace(/\\u200b/g,''));event.preventDefault();event.stopPropagation();}\">\n<div class=\"CodeMirror-code\">\n<div><pre>hug a kitten</pre></div>\n</div>\n</div>\n",
"lines": 1,
"lines_more": 0,
"preview_is_truncated": false,
"has_rich_preview": false,
"file_access": "visible"
}
1 change: 1 addition & 0 deletions tests/golden/SlackWebhookEvent/botMessage.golden
Expand Up @@ -30,6 +30,7 @@ EventEventCallback
{ unConversationId = "C043YJGBY49" }
, text = "TEST"
, channelType = Channel
, files = Nothing
, user = UserId
{ unUserId = "U0442US8QGH" }
, ts = "1664216870.477049"
Expand Down
1 change: 1 addition & 0 deletions tests/golden/SlackWebhookEvent/link.golden
Expand Up @@ -34,6 +34,7 @@ EventEventCallback
{ unConversationId = "C043YJGBY49" }
, text = "<https://jadeapptesting.slack.com/archives/C043YJGBY49/p1663961604007869>"
, channelType = Channel
, files = Nothing
, user = UserId
{ unUserId = "U043H11ES4V" }
, ts = "1663978925.099999"
Expand Down
1 change: 1 addition & 0 deletions tests/golden/SlackWebhookEvent/messageExample.golden
Expand Up @@ -30,6 +30,7 @@ EventEventCallback
{ unConversationId = "C043YJGBY49" }
, text = "dgsfklsdgf"
, channelType = Channel
, files = Nothing
, user = UserId
{ unUserId = "U043H11ES4V" }
, ts = "1663966382.046509"
Expand Down
1 change: 1 addition & 0 deletions tests/golden/SlackWebhookEvent/messageIm.golden
Expand Up @@ -30,6 +30,7 @@ EventEventCallback
{ unConversationId = "D0442US94JD" }
, text = "test"
, channelType = Im
, files = Nothing
, user = UserId
{ unUserId = "U043H11ES4V" }
, ts = "1664408649.009629"
Expand Down