line-bot-sdk 0.5.0.1 → 0.5.0.2
raw patch · 7 files changed
+276/−116 lines, 7 filesdep +exceptionsdep +monad-controldep +mtlPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: exceptions, monad-control, mtl, streaming, transformers-base
API changes (from Hackage documentation)
- Line.Bot.Webhook.Events: SourceGroup :: Id Group -> Maybe (Id User) -> Source
- Line.Bot.Webhook.Events: SourceRoom :: Id Room -> Maybe (Id User) -> Source
- Line.Bot.Webhook.Events: SourceUser :: Id User -> Source
- Line.Bot.Webhook.Events: instance GHC.Classes.Eq Line.Bot.Webhook.Events.Event
- Line.Bot.Webhook.Events: instance GHC.Classes.Eq Line.Bot.Webhook.Events.Events
- Line.Bot.Webhook.Events: instance GHC.Classes.Eq Line.Bot.Webhook.Events.Members
- Line.Bot.Webhook.Events: instance GHC.Classes.Eq Line.Bot.Webhook.Events.Source
+ Line.Bot.Client: broadcastMessage :: [Message] -> Line NoContent
+ Line.Bot.Client: getBroadcastMessageCount :: Day -> Line (Maybe Int)
+ Line.Bot.Client: getGroupMemberUserIds :: Id Group -> Line [Id User]
+ Line.Bot.Client: getGroupMemberUserIdsS :: Id Group -> Stream (Of (Id User)) Line ()
+ Line.Bot.Client: getMessageQuota :: Line Int
+ Line.Bot.Client: getRoomMemberUserIds :: Id Room -> Line [Id User]
+ Line.Bot.Client: getRoomMemberUserIdsS :: Id Room -> Stream (Of (Id User)) Line ()
+ Line.Bot.Client: instance Line.Bot.Client.HasLine (Line.Bot.Client.LineAuth a)
+ Line.Bot.Client: instance Line.Bot.Client.HasLine (a -> Line.Bot.Client.LineAuth b)
+ Line.Bot.Client: instance Line.Bot.Client.HasLine (a -> b -> Line.Bot.Client.LineAuth c)
+ Line.Bot.Internal.Endpoints: type BroadcastMessage = BroadcastMessage' BroadcastMessageBody
+ Line.Bot.Internal.Endpoints: type BroadcastMessage' a = "v2" :> "bot" :> "message" :> "broadcast" :> ReqBody '[JSON] a :> ChannelAuth :> PostNoContent '[JSON] NoContent
+ Line.Bot.Internal.Endpoints: type GetBroadcastMessageCount = GetBroadcastMessageCount' LineDate MessageCount
+ Line.Bot.Internal.Endpoints: type GetBroadcastMessageCount' a b = "v2" :> "bot" :> "message" :> "delivery" :> "broadcast" :> QueryParam' '[Required, Strict] "date" a :> ChannelAuth :> Get '[JSON] b
+ Line.Bot.Internal.Endpoints: type GetGroupMemberUserIds = GetGroupMemberUserIds' MemberIds
+ Line.Bot.Internal.Endpoints: type GetGroupMemberUserIds' a = "v2" :> "bot" :> "group" :> Capture "groupId" (Id Group) :> "members" :> "ids" :> QueryParam "start" String :> ChannelAuth :> Get '[JSON] a
+ Line.Bot.Internal.Endpoints: type GetMessageQuota = GetMessageQuota' MessageQuota
+ Line.Bot.Internal.Endpoints: type GetMessageQuota' a = "v2" :> "bot" :> "message" :> "quota" :> "consumption" :> ChannelAuth :> Get '[JSON] a
+ Line.Bot.Internal.Endpoints: type GetRoomMemberUserIds = GetRoomMemberUserIds' MemberIds
+ Line.Bot.Internal.Endpoints: type GetRoomMemberUserIds' a = "v2" :> "bot" :> "room" :> Capture "roomId" (Id Room) :> "members" :> "ids" :> QueryParam "start" String :> ChannelAuth :> Get '[JSON] a
+ Line.Bot.Types: BroadcastMessageBody :: [Message] -> BroadcastMessageBody
+ Line.Bot.Types: MemberIds :: [Id User] -> Maybe String -> MemberIds
+ Line.Bot.Types: MessageFlex :: Text -> Value -> Maybe QuickReply -> Message
+ Line.Bot.Types: MessageQuota :: Int -> MessageQuota
+ Line.Bot.Types: [$sel:altText:MessageText] :: Message -> Text
+ Line.Bot.Types: [$sel:contents:MessageText] :: Message -> Value
+ Line.Bot.Types: [$sel:memberIds:MemberIds] :: MemberIds -> [Id User]
+ Line.Bot.Types: [$sel:next:MemberIds] :: MemberIds -> Maybe String
+ Line.Bot.Types: [$sel:totalUsage:MessageQuota] :: MessageQuota -> Int
+ Line.Bot.Types: data MemberIds
+ Line.Bot.Types: instance Data.Aeson.Types.FromJSON.FromJSON Line.Bot.Types.MemberIds
+ Line.Bot.Types: instance Data.Aeson.Types.FromJSON.FromJSON Line.Bot.Types.MessageQuota
+ Line.Bot.Types: instance Data.Aeson.Types.ToJSON.ToJSON Line.Bot.Types.BroadcastMessageBody
+ Line.Bot.Types: instance GHC.Classes.Eq Line.Bot.Types.MemberIds
+ Line.Bot.Types: instance GHC.Classes.Eq Line.Bot.Types.MessageQuota
+ Line.Bot.Types: instance GHC.Generics.Generic Line.Bot.Types.BroadcastMessageBody
+ Line.Bot.Types: instance GHC.Generics.Generic Line.Bot.Types.MemberIds
+ Line.Bot.Types: instance GHC.Generics.Generic Line.Bot.Types.MessageQuota
+ Line.Bot.Types: instance GHC.Show.Show Line.Bot.Types.BroadcastMessageBody
+ Line.Bot.Types: instance GHC.Show.Show Line.Bot.Types.MemberIds
+ Line.Bot.Types: instance GHC.Show.Show Line.Bot.Types.MessageQuota
+ Line.Bot.Types: newtype BroadcastMessageBody
+ Line.Bot.Types: newtype MessageQuota
+ Line.Bot.Webhook.Events: Source :: Id a -> Source
+ Line.Bot.Webhook.Events: instance Data.Aeson.Types.ToJSON.ToJSON Line.Bot.Webhook.Events.Source
- Line.Bot.Client: type Line = ReaderT Auth ClientM
+ Line.Bot.Client: type Line = ReaderT ChannelToken ClientM
- Line.Bot.Internal.Endpoints: type GetContent = ChannelAuth :> "v2" :> "bot" :> "message" :> Capture "messageId" MessageId :> "content" :> Get '[OctetStream] ByteString
+ Line.Bot.Internal.Endpoints: type GetContent = "v2" :> "bot" :> "message" :> Capture "messageId" MessageId :> "content" :> ChannelAuth :> Get '[OctetStream] ByteString
- Line.Bot.Internal.Endpoints: type GetContentStream = ChannelAuth :> "v2" :> "bot" :> "message" :> Capture "messageId" MessageId :> "content" :> StreamGet NoFraming OctetStream (SourceIO ByteString)
+ Line.Bot.Internal.Endpoints: type GetContentStream = "v2" :> "bot" :> "message" :> Capture "messageId" MessageId :> "content" :> ChannelAuth :> StreamGet NoFraming OctetStream (SourceIO ByteString)
- Line.Bot.Internal.Endpoints: type GetGroupMemberProfile' a = ChannelAuth :> "v2" :> "bot" :> "group" :> Capture "groupId" (Id Group) :> "member" :> Capture "userId" (Id User) :> Get '[JSON] a
+ Line.Bot.Internal.Endpoints: type GetGroupMemberProfile' a = "v2" :> "bot" :> "group" :> Capture "groupId" (Id Group) :> "member" :> Capture "userId" (Id User) :> ChannelAuth :> Get '[JSON] a
- Line.Bot.Internal.Endpoints: type GetMulticastMessageCount' a b = ChannelAuth :> "v2" :> "bot" :> "message" :> "delivery" :> "multicast" :> QueryParam' '[Required, Strict] "date" a :> Get '[JSON] b
+ Line.Bot.Internal.Endpoints: type GetMulticastMessageCount' a b = "v2" :> "bot" :> "message" :> "delivery" :> "multicast" :> QueryParam' '[Required, Strict] "date" a :> ChannelAuth :> Get '[JSON] b
- Line.Bot.Internal.Endpoints: type GetProfile' a = ChannelAuth :> "v2" :> "bot" :> "profile" :> Capture "userId" (Id User) :> Get '[JSON] a
+ Line.Bot.Internal.Endpoints: type GetProfile' a = "v2" :> "bot" :> "profile" :> Capture "userId" (Id User) :> ChannelAuth :> Get '[JSON] a
- Line.Bot.Internal.Endpoints: type GetPushMessageCount' a b = ChannelAuth :> "v2" :> "bot" :> "message" :> "delivery" :> "push" :> QueryParam' '[Required, Strict] "date" a :> Get '[JSON] b
+ Line.Bot.Internal.Endpoints: type GetPushMessageCount' a b = "v2" :> "bot" :> "message" :> "delivery" :> "push" :> QueryParam' '[Required, Strict] "date" a :> ChannelAuth :> Get '[JSON] b
- Line.Bot.Internal.Endpoints: type GetReplyMessageCount' a b = ChannelAuth :> "v2" :> "bot" :> "message" :> "delivery" :> "reply" :> QueryParam' '[Required, Strict] "date" a :> Get '[JSON] b
+ Line.Bot.Internal.Endpoints: type GetReplyMessageCount' a b = "v2" :> "bot" :> "message" :> "delivery" :> "reply" :> QueryParam' '[Required, Strict] "date" a :> ChannelAuth :> Get '[JSON] b
- Line.Bot.Internal.Endpoints: type GetRoomMemberProfile' a = ChannelAuth :> "v2" :> "bot" :> "room" :> Capture "roomId" (Id Room) :> "member" :> Capture "userId" (Id User) :> Get '[JSON] a
+ Line.Bot.Internal.Endpoints: type GetRoomMemberProfile' a = "v2" :> "bot" :> "room" :> Capture "roomId" (Id Room) :> "member" :> Capture "userId" (Id User) :> ChannelAuth :> Get '[JSON] a
- Line.Bot.Internal.Endpoints: type IssueChannelToken' a b = ReqBody '[FormUrlEncoded] a :> "v2" :> "oauth" :> "accessToken" :> Post '[JSON] b
+ Line.Bot.Internal.Endpoints: type IssueChannelToken' a b = "v2" :> "oauth" :> "accessToken" :> ReqBody '[FormUrlEncoded] a :> Post '[JSON] b
- Line.Bot.Internal.Endpoints: type IssueLinkToken' a = ChannelAuth :> "v2" :> "bot" :> "user" :> Capture "userId" (Id User) :> "linkToken" :> Get '[JSON] a
+ Line.Bot.Internal.Endpoints: type IssueLinkToken' a = "v2" :> "bot" :> "user" :> Capture "userId" (Id User) :> "linkToken" :> ChannelAuth :> Get '[JSON] a
- Line.Bot.Internal.Endpoints: type LeaveGroup = ChannelAuth :> "v2" :> "bot" :> "group" :> Capture "groupId" (Id Group) :> "leave" :> PostNoContent '[JSON] NoContent
+ Line.Bot.Internal.Endpoints: type LeaveGroup = "v2" :> "bot" :> "group" :> Capture "groupId" (Id Group) :> "leave" :> ChannelAuth :> PostNoContent '[JSON] NoContent
- Line.Bot.Internal.Endpoints: type LeaveRoom = ChannelAuth :> "v2" :> "bot" :> "room" :> Capture "roomId" (Id Room) :> "leave" :> PostNoContent '[JSON] NoContent
+ Line.Bot.Internal.Endpoints: type LeaveRoom = "v2" :> "bot" :> "room" :> Capture "roomId" (Id Room) :> "leave" :> ChannelAuth :> PostNoContent '[JSON] NoContent
- Line.Bot.Internal.Endpoints: type MulticastMessage' a = ChannelAuth :> "v2" :> "bot" :> "message" :> "multicast" :> ReqBody '[JSON] a :> PostNoContent '[JSON] NoContent
+ Line.Bot.Internal.Endpoints: type MulticastMessage' a = "v2" :> "bot" :> "message" :> "multicast" :> ReqBody '[JSON] a :> ChannelAuth :> PostNoContent '[JSON] NoContent
- Line.Bot.Internal.Endpoints: type PushMessage' a = ChannelAuth :> "v2" :> "bot" :> "message" :> "push" :> ReqBody '[JSON] a :> PostNoContent '[JSON] NoContent
+ Line.Bot.Internal.Endpoints: type PushMessage' a = "v2" :> "bot" :> "message" :> "push" :> ReqBody '[JSON] a :> ChannelAuth :> PostNoContent '[JSON] NoContent
- Line.Bot.Internal.Endpoints: type ReplyMessage' a = ChannelAuth :> "v2" :> "bot" :> "message" :> "reply" :> ReqBody '[JSON] a :> PostNoContent '[JSON] NoContent
+ Line.Bot.Internal.Endpoints: type ReplyMessage' a = "v2" :> "bot" :> "message" :> "reply" :> ReqBody '[JSON] a :> ChannelAuth :> PostNoContent '[JSON] NoContent
- Line.Bot.Internal.Endpoints: type RevokeChannelToken' a = ReqBody '[FormUrlEncoded] a :> "v2" :> "oauth" :> "revoke" :> PostNoContent '[JSON] NoContent
+ Line.Bot.Internal.Endpoints: type RevokeChannelToken' a = "v2" :> "oauth" :> "revoke" :> ReqBody '[FormUrlEncoded] a :> PostNoContent '[JSON] NoContent
- Line.Bot.Webhook.Events: MessageLocation :: MessageId -> Text -> Text -> Double -> Double -> Message
+ Line.Bot.Webhook.Events: MessageLocation :: MessageId -> Maybe Text -> Text -> Double -> Double -> Message
- Line.Bot.Webhook.Events: [$sel:title:MessageText] :: Message -> Text
+ Line.Bot.Webhook.Events: [$sel:title:MessageText] :: Message -> Maybe Text
Files
- README.md +17/−9
- line-bot-sdk.cabal +6/−1
- src/Line/Bot/Client.hs +105/−34
- src/Line/Bot/Internal/Endpoints.hs +94/−52
- src/Line/Bot/Types.hs +25/−0
- src/Line/Bot/Webhook/Events.hs +20/−12
- test/Line/Bot/ClientSpec.hs +9/−8
README.md view
@@ -41,21 +41,24 @@ ## Usage ```haskell+{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} -import Data.String (fromString)-import Line.Bot.Client (Line, getProfile, runLine)-import Line.Bot.Types (Profile)-import System.Environment (getEnv)+import Data.String (fromString)+import Line.Bot.Client+import Line.Bot.Types+import System.Environment (getEnv) -profile :: Line Profile-profile = getProfile "U4af4980629..."+getProfiles :: Id Room -> Line [Profile]+getProfiles roomId = do+ userIds <- getRoomMemberUserIds roomId+ sequence $ getRoomMemberProfile roomId <$> userIds main = do- token <- fromString <$> getEnv "CHANNEL_TOKEN"- result <- runLine profile token+ token <- fromString <$> getEnv "CHANNEL_TOKEN"+ result <- runLine (getProfiles "U4af4980629...") token case result of- Left err -> print err+ Left err -> print err Right profile -> print profile ``` @@ -66,3 +69,8 @@ Please report bugs via the [github issue tracker](https://github.com/moleike/line-bot-sdk/issues).++## Acknowledgments++Thanks to the authors of [servant-github](https://hackage.haskell.org/package/servant-github), for inspiration.+
line-bot-sdk.cabal view
@@ -1,5 +1,5 @@ name: line-bot-sdk-version: 0.5.0.1+version: 0.5.0.2 synopsis: Haskell SDK for LINE Messaging API homepage: https://github.com/moleike/line-bot-sdk#readme bug-reports: https://github.com/moleike/line-bot-sdk/issues@@ -55,6 +55,11 @@ , servant-server >= 0.15 && < 0.16 , wai >= 3.2.2 && < 3.3 , wai-extra >= 3.0.25 && < 3.1+ , exceptions >= 0.10 && < 0.11+ , transformers-base >= 0.4 && < 0.5+ , mtl >= 2.2.2 && < 2.3+ , monad-control >= 1.0 && < 1.1+ , streaming >= 0.2 && < 0.3 default-language: Haskell2010
src/Line/Bot/Client.hs view
@@ -3,8 +3,11 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}+ -- | -- Module : Line.Bot.Client -- Copyright : (c) Alexandre Moreno, 2019@@ -22,17 +25,24 @@ -- ** Group , getGroupMemberProfile , leaveGroup+ , getGroupMemberUserIds+ , getGroupMemberUserIdsS -- ** Room , getRoomMemberProfile , leaveRoom+ , getRoomMemberUserIds+ , getRoomMemberUserIdsS -- ** Message , replyMessage , pushMessage , multicastMessage+ , broadcastMessage , getContent , getPushMessageCount , getReplyMessageCount , getMulticastMessageCount+ , getBroadcastMessageCount+ , getMessageQuota -- ** Account Link , issueLinkToken -- ** OAuth@@ -41,25 +51,28 @@ ) where +import Control.Monad.IO.Class+import Control.Monad.Reader import Control.Monad.Trans.Class (lift)-import Control.Monad.Trans.Reader (ReaderT, ask, runReaderT)-import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy as LB import Data.Proxy import Data.Time.Calendar (Day)-import Line.Bot.Internal.Auth+import Line.Bot.Internal.Auth (Auth, mkAuth) import Line.Bot.Internal.Endpoints import Line.Bot.Types import Network.HTTP.Client (newManager) import Network.HTTP.Client.TLS (tlsManagerSettings)-import Servant.API+import Servant.API hiding (Stream) import Servant.Client+import Streaming+import qualified Streaming.Prelude as S host :: BaseUrl host = BaseUrl Https "api.line.me" 443 "" -- | @Line@ is the monad in which bot requests run. Contains the -- OAuth access token for a channel-type Line = ReaderT Auth ClientM+type Line = ReaderT ChannelToken ClientM withLineEnv :: (ClientEnv -> IO a) -> IO a withLineEnv app = do@@ -72,78 +85,136 @@ -- | Runs a @Line@ computation with the given channel access token runLine :: Line a -> ChannelToken -> IO (Either ServantError a)-runLine comp = runLine' . runReaderT comp . mkAuth+runLine comp = runLine' . runReaderT comp +type LineAuth a = Auth -> ClientM a++type family AddLineAuth a :: * where+ AddLineAuth (LineAuth a) = Line a+ AddLineAuth (a -> b) = a -> AddLineAuth b++class HasLine a where+ addLineAuth :: a -> AddLineAuth a++instance HasLine (LineAuth a) where+ addLineAuth comp = ask >>= \token -> lift $ comp (mkAuth token)++instance HasLine (a -> LineAuth b) where+ addLineAuth comp = addLineAuth . comp++instance HasLine (a -> b -> LineAuth c) where+ addLineAuth comp = addLineAuth . comp++line :: (HasLine (Client ClientM api), HasClient ClientM api)+ => Proxy api -> AddLineAuth (Client ClientM api)+line api = addLineAuth (client api)+ getProfile :: Id User -> Line Profile-getProfile a = ask >>= \auth ->- lift $ client (Proxy :: Proxy GetProfile) auth a+getProfile = line (Proxy :: Proxy GetProfile) getGroupMemberProfile :: Id Group -> Id User -> Line Profile-getGroupMemberProfile a b = ask >>= \auth ->- lift $ client (Proxy :: Proxy GetGroupMemberProfile) auth a b+getGroupMemberProfile = line (Proxy :: Proxy GetGroupMemberProfile) leaveGroup :: Id Group -> Line NoContent-leaveGroup a = ask >>= \auth ->- lift $ client (Proxy :: Proxy LeaveGroup) auth a+leaveGroup = line (Proxy :: Proxy LeaveGroup) +getGroupMemberUserIds' :: Id Group -> Maybe String -> Line MemberIds+getGroupMemberUserIds' = line (Proxy :: Proxy GetGroupMemberUserIds)++getGroupMemberUserIdsS :: Id Group -> Stream (Of (Id User)) Line ()+getGroupMemberUserIdsS gid = go gid Nothing+ where+ go gid token = do+ MemberIds{..} <- lift $ getGroupMemberUserIds' gid token+ S.each memberIds+ case next of+ Nothing -> return ()+ token' -> go gid token'++getGroupMemberUserIds :: Id Group -> Line [Id User]+getGroupMemberUserIds = S.toList_ . getGroupMemberUserIdsS+ getRoomMemberProfile :: Id Room -> Id User -> Line Profile-getRoomMemberProfile a b = ask >>= \auth ->- lift $ client (Proxy :: Proxy GetRoomMemberProfile) auth a b+getRoomMemberProfile = line (Proxy :: Proxy GetRoomMemberProfile) leaveRoom :: Id Room -> Line NoContent-leaveRoom a = ask >>= \auth ->- lift $ client (Proxy :: Proxy LeaveRoom) auth a+leaveRoom = line (Proxy :: Proxy LeaveRoom) +getRoomMemberUserIds' :: Id Room -> Maybe String -> Line MemberIds+getRoomMemberUserIds' = line (Proxy :: Proxy GetRoomMemberUserIds)++getRoomMemberUserIdsS :: Id Room -> Stream (Of (Id User)) Line ()+getRoomMemberUserIdsS gid = go gid Nothing+ where+ go gid token = do+ MemberIds{..} <- lift $ getRoomMemberUserIds' gid token+ S.each memberIds+ case next of+ Nothing -> return ()+ token' -> go gid token'++getRoomMemberUserIds :: Id Room -> Line [Id User]+getRoomMemberUserIds = S.toList_ . getRoomMemberUserIdsS+ replyMessage' :: ReplyMessageBody -> Line NoContent-replyMessage' a = ask >>= \auth ->- lift $ client (Proxy :: Proxy ReplyMessage) auth a+replyMessage' = line (Proxy :: Proxy ReplyMessage) replyMessage :: ReplyToken -> [Message] -> Line NoContent replyMessage a ms = replyMessage' (ReplyMessageBody a ms) pushMessage' :: PushMessageBody -> Line NoContent-pushMessage' a = ask >>= \auth ->- lift $ client (Proxy :: Proxy PushMessage) auth a+pushMessage' = line (Proxy :: Proxy PushMessage) pushMessage :: Id a -> [Message] -> Line NoContent pushMessage a ms = pushMessage' (PushMessageBody a ms) multicastMessage' :: MulticastMessageBody -> Line NoContent-multicastMessage' a = ask >>= \auth ->- lift $ client (Proxy :: Proxy MulticastMessage) auth a+multicastMessage' = line (Proxy :: Proxy MulticastMessage) multicastMessage :: [Id User] -> [Message] -> Line NoContent multicastMessage a ms = multicastMessage' (MulticastMessageBody a ms) --- | TODO: this should use a streaming library for constant memory usage over large data-getContent :: MessageId -> Line ByteString-getContent a = ask >>= \auth ->- lift $ client (Proxy :: Proxy GetContent) auth a+broadcastMessage' :: BroadcastMessageBody -> Line NoContent+broadcastMessage' = line (Proxy :: Proxy BroadcastMessage) +broadcastMessage :: [Message] -> Line NoContent+broadcastMessage = broadcastMessage' . BroadcastMessageBody++getContent :: MessageId -> Line LB.ByteString+getContent = line (Proxy :: Proxy GetContent)+ getPushMessageCount' :: LineDate -> Line MessageCount-getPushMessageCount' a = ask >>= \auth ->- lift $ client (Proxy :: Proxy GetPushMessageCount) auth a+getPushMessageCount' = line (Proxy :: Proxy GetPushMessageCount) getPushMessageCount :: Day -> Line (Maybe Int) getPushMessageCount = fmap count . getPushMessageCount' . LineDate getReplyMessageCount' :: LineDate -> Line MessageCount-getReplyMessageCount' a = ask >>= \auth ->- lift $ client (Proxy :: Proxy GetReplyMessageCount) auth a+getReplyMessageCount' = line (Proxy :: Proxy GetReplyMessageCount) getReplyMessageCount :: Day -> Line (Maybe Int) getReplyMessageCount = fmap count . getReplyMessageCount' . LineDate getMulticastMessageCount' :: LineDate -> Line MessageCount-getMulticastMessageCount' a = ask >>= \auth ->- lift $ client (Proxy :: Proxy GetMulticastMessageCount) auth a+getMulticastMessageCount' = line (Proxy :: Proxy GetMulticastMessageCount) getMulticastMessageCount :: Day -> Line (Maybe Int) getMulticastMessageCount = fmap count . getMulticastMessageCount' . LineDate +getBroadcastMessageCount' :: LineDate -> Line MessageCount+getBroadcastMessageCount' = line (Proxy :: Proxy GetBroadcastMessageCount)++getBroadcastMessageCount :: Day -> Line (Maybe Int)+getBroadcastMessageCount = fmap count . getBroadcastMessageCount' . LineDate++getMessageQuota' :: Line MessageQuota+getMessageQuota' = line (Proxy :: Proxy GetMessageQuota)++getMessageQuota :: Line Int+getMessageQuota = fmap totalUsage getMessageQuota'+ issueLinkToken :: Id User -> Line LinkToken-issueLinkToken a = ask >>= \auth ->- lift $ client (Proxy :: Proxy IssueLinkToken) auth a+issueLinkToken = line (Proxy :: Proxy IssueLinkToken) issueChannelToken' :: ClientCredentials -> ClientM ShortLivedChannelToken issueChannelToken' = client (Proxy :: Proxy IssueChannelToken)
src/Line/Bot/Internal/Endpoints.hs view
@@ -23,142 +23,184 @@ -- | Combinator for authenticating with the channel access token type ChannelAuth = AuthProtect "channel-access-token" -type GetProfile' a = ChannelAuth- :> "v2" :> "bot"- :> "profile"+type GetProfile' a =+ "v2":> "bot" :> "profile" :> Capture "userId" (Id User)+ :> ChannelAuth :> Get '[JSON] a type GetProfile = GetProfile' Profile -type GetGroupMemberProfile' a = ChannelAuth- :> "v2" :> "bot"- :> "group"+type GetGroupMemberProfile' a =+ "v2":> "bot" :> "group" :> Capture "groupId" (Id Group) :> "member" :> Capture "userId" (Id User)+ :> ChannelAuth :> Get '[JSON] a type GetGroupMemberProfile = GetGroupMemberProfile' Profile -type LeaveGroup = ChannelAuth- :> "v2" :> "bot"- :> "group"+type LeaveGroup =+ "v2":> "bot" :> "group" :> Capture "groupId" (Id Group) :> "leave"+ :> ChannelAuth :> PostNoContent '[JSON] NoContent -type GetRoomMemberProfile' a = ChannelAuth- :> "v2" :> "bot"- :> "room"+type GetGroupMemberUserIds' a =+ "v2":> "bot" :> "group"+ :> Capture "groupId" (Id Group)+ :> "members"+ :> "ids"+ :> QueryParam "start" String+ :> ChannelAuth+ :> Get '[JSON] a++type GetGroupMemberUserIds = GetGroupMemberUserIds' MemberIds++type GetRoomMemberProfile' a =+ "v2":> "bot" :> "room" :> Capture "roomId" (Id Room) :> "member" :> Capture "userId" (Id User)+ :> ChannelAuth :> Get '[JSON] a type GetRoomMemberProfile = GetRoomMemberProfile' Profile -type LeaveRoom = ChannelAuth- :> "v2" :> "bot"- :> "room"+type LeaveRoom =+ "v2":> "bot" :> "room" :> Capture "roomId" (Id Room) :> "leave"+ :> ChannelAuth :> PostNoContent '[JSON] NoContent -type ReplyMessage' a = ChannelAuth- :> "v2" :> "bot"- :> "message"- :> "reply"+type GetRoomMemberUserIds' a =+ "v2":> "bot" :> "room"+ :> Capture "roomId" (Id Room)+ :> "members"+ :> "ids"+ :> QueryParam "start" String+ :> ChannelAuth+ :> Get '[JSON] a++type GetRoomMemberUserIds = GetRoomMemberUserIds' MemberIds++type ReplyMessage' a =+ "v2":> "bot" :> "message" :> "reply" :> ReqBody '[JSON] a+ :> ChannelAuth :> PostNoContent '[JSON] NoContent type ReplyMessage = ReplyMessage' ReplyMessageBody -type PushMessage' a = ChannelAuth- :> "v2" :> "bot"- :> "message"+type PushMessage' a =+ "v2":> "bot" :> "message" :> "push" :> ReqBody '[JSON] a+ :> ChannelAuth :> PostNoContent '[JSON] NoContent type PushMessage = PushMessage' PushMessageBody -type MulticastMessage' a = ChannelAuth- :> "v2" :> "bot"- :> "message"+type MulticastMessage' a =+ "v2":> "bot" :> "message" :> "multicast" :> ReqBody '[JSON] a+ :> ChannelAuth :> PostNoContent '[JSON] NoContent type MulticastMessage = MulticastMessage' MulticastMessageBody -type GetContent = ChannelAuth- :> "v2" :> "bot"- :> "message"+type BroadcastMessage' a =+ "v2":> "bot" :> "message"+ :> "broadcast"+ :> ReqBody '[JSON] a+ :> ChannelAuth+ :> PostNoContent '[JSON] NoContent++type BroadcastMessage = BroadcastMessage' BroadcastMessageBody++type GetContent =+ "v2":> "bot" :> "message" :> Capture "messageId" MessageId :> "content"+ :> ChannelAuth :> Get '[OctetStream] ByteString -type GetContentStream = ChannelAuth- :> "v2" :> "bot"- :> "message"+type GetContentStream =+ "v2":> "bot" :> "message" :> Capture "messageId" MessageId :> "content"+ :> ChannelAuth :> StreamGet NoFraming OctetStream (SourceIO ByteString) -type GetReplyMessageCount' a b = ChannelAuth- :> "v2" :> "bot"- :> "message"- :> "delivery"+type GetReplyMessageCount' a b =+ "v2":> "bot" :> "message" :> "delivery" :> "reply" :> QueryParam' '[Required, Strict] "date" a+ :> ChannelAuth :> Get '[JSON] b type GetReplyMessageCount = GetReplyMessageCount' LineDate MessageCount -type GetPushMessageCount' a b = ChannelAuth- :> "v2" :> "bot"- :> "message"- :> "delivery"+type GetPushMessageCount' a b =+ "v2":> "bot" :> "message" :> "delivery" :> "push" :> QueryParam' '[Required, Strict] "date" a+ :> ChannelAuth :> Get '[JSON] b type GetPushMessageCount = GetPushMessageCount' LineDate MessageCount -type GetMulticastMessageCount' a b = ChannelAuth- :> "v2" :> "bot"- :> "message"- :> "delivery"+type GetMulticastMessageCount' a b =+ "v2" :> "bot" :> "message" :> "delivery" :> "multicast" :> QueryParam' '[Required, Strict] "date" a+ :> ChannelAuth :> Get '[JSON] b type GetMulticastMessageCount = GetMulticastMessageCount' LineDate MessageCount -type IssueLinkToken' a = ChannelAuth- :> "v2" :> "bot"- :> "user"+type GetBroadcastMessageCount' a b =+ "v2" :> "bot" :> "message" :> "delivery"+ :> "broadcast"+ :> QueryParam' '[Required, Strict] "date" a+ :> ChannelAuth+ :> Get '[JSON] b++type GetBroadcastMessageCount = GetBroadcastMessageCount' LineDate MessageCount++type GetMessageQuota' a =+ "v2":> "bot" :> "message" :> "quota"+ :> "consumption"+ :> ChannelAuth+ :> Get '[JSON] a++type GetMessageQuota = GetMessageQuota' MessageQuota++type IssueLinkToken' a =+ "v2":> "bot" :> "user" :> Capture "userId" (Id User) :> "linkToken"+ :> ChannelAuth :> Get '[JSON] a type IssueLinkToken = IssueLinkToken' LinkToken type IssueChannelToken' a b =- ReqBody '[FormUrlEncoded] a- :> "v2"- :> "oauth"+ "v2" :> "oauth" :> "accessToken"+ :> ReqBody '[FormUrlEncoded] a :> Post '[JSON] b type IssueChannelToken = IssueChannelToken' ClientCredentials ShortLivedChannelToken type RevokeChannelToken' a =- ReqBody '[FormUrlEncoded] a- :> "v2"- :> "oauth"+ "v2" :> "oauth" :> "revoke"+ :> ReqBody '[FormUrlEncoded] a :> PostNoContent '[JSON] NoContent type RevokeChannelToken = RevokeChannelToken' ChannelToken
src/Line/Bot/Types.hs view
@@ -33,6 +33,7 @@ , ReplyMessageBody(ReplyMessageBody) , PushMessageBody(PushMessageBody) , MulticastMessageBody(MulticastMessageBody)+ , BroadcastMessageBody(BroadcastMessageBody) , Profile(..) , QuickReply(..) , QuickReplyButton(..)@@ -41,6 +42,8 @@ , ShortLivedChannelToken(..) , LineDate(..) , MessageCount(..)+ , MessageQuota(..)+ , MemberIds(..) ) where @@ -177,6 +180,10 @@ , longitude :: Double , quickReply :: Maybe QuickReply }+ | MessageFlex { altText :: Text+ , contents :: Value+ , quickReply :: Maybe QuickReply+ } deriving (Eq, Show, Generic) instance ToJSON Message where@@ -242,6 +249,12 @@ instance ToJSON MulticastMessageBody +newtype BroadcastMessageBody = BroadcastMessageBody+ { messages :: [Message] }+ deriving (Show, Generic)++instance ToJSON BroadcastMessageBody+ newtype QuickReply = QuickReply { items :: [QuickReplyButton] } deriving (Eq, Show, Generic)@@ -333,3 +346,15 @@ count <- o .:? "success" status <- o .: "status" return MessageCount{..}++newtype MessageQuota = MessageQuota { totalUsage :: Int }+ deriving (Eq, Show, Generic)++instance FromJSON MessageQuota++data MemberIds = MemberIds+ { memberIds :: [Id User]+ , next :: Maybe String+ } deriving (Eq, Show, Generic)++instance FromJSON MemberIds
src/Line/Bot/Webhook/Events.hs view
@@ -4,9 +4,13 @@ {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ExtendedDefaultRules #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE StandaloneDeriving #-}+ -- | -- Module : Line.Bot.Webhook.Events -- Copyright : (c) Alexandre Moreno, 2019@@ -56,7 +60,7 @@ { destination :: Id User -- ^ User ID of a bot that should receive webhook events , events :: [Event] -- ^ List of webhook event objects }- deriving (Eq, Show, Generic)+ deriving (Show, Generic) instance FromJSON Events @@ -110,7 +114,7 @@ , timestamp :: EpochMilli , things :: Things }- deriving (Eq, Show, Generic)+ deriving (Show, Generic) instance FromJSON Event where parseJSON = genericParseJSON defaultOptions@@ -141,7 +145,7 @@ , fileName :: Text } | MessageLocation { messageId :: MessageId- , title :: Text+ , title :: Maybe Text , address :: Text , latitude :: Double , longitude :: Double@@ -189,24 +193,28 @@ . toRational . (/ 1000) -data Source =- SourceUser (Id User)- | SourceGroup (Id Group) (Maybe (Id User))- | SourceRoom (Id Room) (Maybe (Id User))- deriving (Eq, Show)+data Source = forall a. Source (Id a) +deriving instance Show Source+ instance FromJSON Source where parseJSON = withObject "Source" $ \o -> do messageType <- o .: "type" case messageType of- "user" -> SourceUser <$> o .: "userId"- "group" -> SourceGroup <$> o .: "groupId" <*> o .:? "userId"- "room" -> SourceRoom <$> o .: "roomId" <*> o .:? "userId"+ "user" -> (Source . UserId) <$> o .: "userId"+ "group" -> (Source . GroupId) <$> o .: "groupId"+ "room" -> (Source . RoomId) <$> o .: "roomId" _ -> fail ("unknown source: " ++ messageType) +instance ToJSON Source where+ toJSON (Source (UserId a)) = object ["type" .= "user", "userId" .= a]+ toJSON (Source (GroupId a)) = object ["type" .= "group", "groupId" .= a]+ toJSON (Source (RoomId a)) = object ["type" .= "room", "roomId" .= a]++ newtype Members = Members { members :: [Source] }- deriving (Eq, Show, Generic)+ deriving (Show, Generic) instance FromJSON Members
test/Line/Bot/ClientSpec.hs view
@@ -56,13 +56,13 @@ :<|> GetGroupMemberProfile' Value :<|> GetRoomMemberProfile' Value -getReplyMessageCountF :: Auth -> LineDate -> Free ClientF MessageCount+getReplyMessageCountF :: LineDate -> Auth -> Free ClientF MessageCount getReplyMessageCountF = F.client (Proxy :: Proxy GetReplyMessageCount) -getPushMessageCountF :: Auth -> LineDate -> Free ClientF MessageCount+getPushMessageCountF :: LineDate -> Auth -> Free ClientF MessageCount getPushMessageCountF = F.client (Proxy :: Proxy GetPushMessageCount) -getMulticastMessageCountF :: Auth -> LineDate -> Free ClientF MessageCount+getMulticastMessageCountF :: LineDate -> Auth -> Free ClientF MessageCount getMulticastMessageCountF = F.client (Proxy :: Proxy GetMulticastMessageCount) testProfile :: Value@@ -80,10 +80,11 @@ manager <- newManager defaultManagerSettings app $ mkClientEnv manager $ BaseUrl Http "localhost" port "" -auth = mkAuth "fake"+token :: ChannelToken+token = "fake" runLine :: Line a -> Port -> IO (Either ServantError a)-runLine comp port = withPort port $ runClientM $ runReaderT comp auth+runLine comp port = withPort port $ runClientM $ runReaderT comp token app :: Application app = serveWithContext (Proxy :: Proxy API) serverContext $@@ -106,15 +107,15 @@ runLine (getRoomMemberProfile "1" "1") >=> (`shouldSatisfy` isRight) it "should send `date` query param for push message count" $ do- let Free (RunRequest Request{..} _) = getPushMessageCountF auth date+ let Free (RunRequest Request{..} _) = getPushMessageCountF date (mkAuth token) toList requestQueryString `shouldBe` [("date", Just "20190407")] it "should send `date` query param for reply message count" $ do- let Free (RunRequest Request{..} _) = getReplyMessageCountF auth date+ let Free (RunRequest Request{..} _) = getReplyMessageCountF date (mkAuth token) toList requestQueryString `shouldBe` [("date", Just "20190407")] it "should send `date` query param for multicast message count" $ do- let Free (RunRequest Request{..} _) = getMulticastMessageCountF auth date+ let Free (RunRequest Request{..} _) = getMulticastMessageCountF date (mkAuth token) toList requestQueryString `shouldBe` [("date", Just "20190407")] where date = LineDate $ fromGregorian 2019 4 7