packages feed

twitchapi-0.0.3: src/Web/TwitchAPI/Helix/Users.hs

{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE RecordWildCards     #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Web.TwitchAPI.Helix.Users where

import Prelude

import Data.Functor  ( (<&>) )

import qualified Data.ByteString.Char8 as BS
import qualified Data.Time             as Time
import qualified Data.Time.RFC3339     as Time ( parseTimeRFC3339 )
import qualified Network.HTTP.Client   as HTTP

import Data.Aeson        ( FromJSON(..), (.:), withObject )
import Data.Aeson.KeyMap ( toAscList )

import qualified Web.TwitchAPI.Helix.Request as Req

-- BUG: Not yet implemented:
--   - Update User
--   - Block/Unblock User
--   - Update User Extensions

class DisplayName a where
    displayName :: a -> String

class ExtensionId a where
    extensionId :: a -> String

class IsActive a where
    active :: a -> Bool

class Named a where
    name :: a -> String

class UserId a where
    userId :: a -> Integer

class Versioned a where
    version :: a -> String

data User = User { lookupId :: Maybe String
                 , username :: Maybe String
                 } deriving ( Show )

instance Req.HelixRequest User where
    toRequest user =
        let lookupId' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\i -> [("id", Just . BS.pack . show $ i)]) (lookupId user)
            username' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\u -> [("login", Just . BS.pack $ u)]) (username user)
            setQuery = HTTP.setQueryString $ lookupId' ++ username'
        in setQuery $ HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users"
    scope User{} = Just "user:read:email"

data BroadcasterType = Partner | Affiliate | None deriving ( Eq, Show )

instance Read BroadcasterType where
    readsPrec _ "partner"   = [(Partner, "")]
    readsPrec _ "affiliate" = [(Affiliate, "")]
    readsPrec _ _           = [(None, "")]

data UserType = Staff | Admin | GlobalMod | NormalUser deriving ( Eq, Show )

instance Read UserType where
    readsPrec _ "staff"      = [(Staff, "")]
    readsPrec _ "admin"      = [(Admin, "")]
    readsPrec _ "global_mod" = [(GlobalMod, "")]
    readsPrec _ _            = [(NormalUser, "")]

data UserEntry = UserEntry { broadcasterType :: BroadcasterType
                           , description     :: String
                           , userDisplayName :: String
                           , userEntryId     :: Integer
                           , login           :: String
                           , offlineImageURL :: String
                           , profileImageURL :: String
                           , userType        :: UserType
                           , email           :: Maybe String
                           , createdAt       :: Maybe Time.UTCTime
                           } deriving ( Show, Eq )

instance FromJSON UserEntry where
    parseJSON = withObject "UserEntry" $ \o -> do
        userId' :: String <- o .: "id"
        created :: String <- o .: "created_at"
        userType' :: String <- o .: "type"
        broadcasterType' :: String <- o .: "broadcaster_type"
        let userEntryId = read userId' :: Integer
            createdAt = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 created
            userType = read userType' :: UserType
            broadcasterType = read broadcasterType' :: BroadcasterType

        description <- o .: "description"
        userDisplayName <- o .: "display_name"
        login <- o .: "login"
        offlineImageURL <- o .: "offline_image_url"
        profileImageURL <- o .: "profile_image_url"
        email <- o .: "email"
        return UserEntry{..}

instance DisplayName UserEntry where
    displayName = userDisplayName

instance UserId UserEntry where
    userId = userEntryId

data Users = Users { lookupIds :: [Integer]
                   , usernames :: [String]
                   } deriving ( Show, Eq )

instance Req.HelixRequest Users where
    toRequest users =
        let lookupId' :: [(BS.ByteString, Maybe BS.ByteString)] = (\i -> ("id", Just . BS.pack . show $ i)) <$> lookupIds users
            username' :: [(BS.ByteString, Maybe BS.ByteString)] = (\u -> ("login", Just . BS.pack $ u)) <$> usernames users
            setQuery = HTTP.setQueryString $ lookupId' ++ username'
        in setQuery $ HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users"
    scope Users{} = Nothing

newtype UsersResponse = UsersResponse { users :: [UserEntry] } deriving ( Eq, Show )

instance FromJSON UsersResponse where
    parseJSON = withObject "UsersResponse" $ \o -> do
        users <- o .: "data"
        return UsersResponse{..}

data Follows = Follows { after :: Maybe String
                       , max :: Maybe Integer
                       , from :: Maybe Integer
                       , to :: Maybe Integer
                       } deriving ( Show, Eq )

instance Req.HelixRequest Follows where
    toRequest user =
        let after' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\a -> [("after", Just . BS.pack $ a)]) (after user)
            max' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\u -> [("first", Just . BS.pack . show $ u)]) (from user)
            fromId' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\u -> [("after", Just . BS.pack . show $ u)]) (from user)
            toId' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\u -> [("after", Just . BS.pack . show $ u)]) (to user)
            setQuery = HTTP.setQueryString $ concat [after', max', fromId', toId']
        in setQuery $ HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users/follows"
    scope Follows{} = Nothing

data FollowEntry = FollowEntry { fromId :: Integer
                               , fromLogin :: String
                               , fromName :: String
                               , toId :: Integer
                               , toName :: String
                               , followedAt :: Maybe Time.UTCTime
                               } deriving ( Show, Eq )

instance FromJSON FollowEntry where
    parseJSON = withObject "FollowEntry" $ \o -> do
        fromId' :: String <- o .: "from_id"
        toId' :: String <- o .: "to_id"
        followedAt' :: String <- o .: "followed_at"
        let fromId = read fromId' :: Integer
            toId = read toId' :: Integer
            followedAt = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 followedAt'
        fromLogin <- o .: "from_login"
        fromName <- o .: "from_name"
        toName <- o .: "to_name"
        return FollowEntry{..}

data FollowsResponse = FollowsResponse { total :: Integer
                                       , follows :: [FollowEntry]
                                       , paginationCursor :: String
                                       } deriving ( Show, Eq )

instance FromJSON FollowsResponse where
    parseJSON = withObject "FollowsResponse" $ \o -> do
        total <- o .: "total"
        paginationCursor <- (o .: "pagination") >>= (.: "cursor")
        follows <- o .: "data"
        return FollowsResponse{..}

newtype BlockList = BlockList { broadcasterId :: Integer } deriving ( Show, Eq )

instance Req.HelixRequest BlockList where
    toRequest user =
        HTTP.setQueryString [("login", Just . BS.pack . show . broadcasterId $ user)] $
            HTTP.parseRequest_ "https://api.twitch.tv/helix/users/blocks"
    scope BlockList{} = Just "user:read:blocked_users"

data BlockListEntry = BlockListEntry { blockedUserId :: Integer
                                     , userLogin :: String
                                     , blockedDisplayName :: String
                                     } deriving ( Show, Eq )

instance FromJSON BlockListEntry where
    parseJSON = withObject "BlockListEntry" $ \o -> do
        userId' :: String <- o .: "user_id"
        let blockedUserId = read userId' :: Integer
        userLogin <- o .: "user_login"
        blockedDisplayName <- o .: "display_name"
        return BlockListEntry{..}

instance DisplayName BlockListEntry where
    displayName = blockedDisplayName

instance UserId BlockListEntry where
    userId = blockedUserId

newtype BlockListResponse = BlockListResponse { blocks :: [BlockListEntry] } deriving ( Show, Eq )

instance FromJSON BlockListResponse where
    parseJSON = withObject "BlockListResponse" $ \o -> do
        blocks <- o .: "data"
        return BlockListResponse{..}

data Extensions = Extensions

instance Req.HelixRequest Extensions where
    toRequest _ = HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users/extensions/list"
    scope Extensions = Just "user:read:broadcast"

data ExtensionType = Component | Mobile | Panel | Overlay deriving ( Show, Eq )

instance Read ExtensionType where
    readsPrec _ "component" = [(Component, "")]
    readsPrec _ "mobile"    = [(Mobile, "")]
    readsPrec _ "panel"     = [(Panel, "")]
    readsPrec _ "overlay"   = [(Overlay, "")]
    readsPrec _ _           = mempty

data ExtensionsEntry = ExtensionsEntry { canActivate :: Bool
                                       , extensionEntryId :: String
                                       , extensionName :: String
                                       , extensionTypes :: [ExtensionType]
                                       , extensionVersion :: String
                                       } deriving ( Show, Eq )

instance ExtensionId ExtensionsEntry where
    extensionId = extensionEntryId

instance Versioned ExtensionsEntry where
    version = extensionVersion

instance FromJSON ExtensionsEntry where
    parseJSON = withObject "ExtensionsEntry" $ \o -> do
        extensionTypes' :: [String] <- o .: "type"
        let extensionTypes :: [ExtensionType] = read <$> extensionTypes'
        canActivate <- o .: "can_activate"
        extensionEntryId <- o .: "id"
        extensionName <- o .: "name"
        extensionVersion <- o .: "version"
        return ExtensionsEntry{..}

newtype ExtensionsResponse = ExtensionsResponse { extensions :: [ExtensionsEntry] } deriving ( Show, Eq )

instance FromJSON ExtensionsResponse where
    parseJSON = withObject "ExtensionsResponse" $ \o -> do
        extensions <- o .: "data"
        return ExtensionsResponse{..}

data ActiveExtensions = ActiveExtensions
                      | ActiveExtensionsFor Integer deriving ( Show, Eq )

instance Req.HelixRequest ActiveExtensions where
    toRequest (ActiveExtensionsFor i) =
        let setQuery = HTTP.setQueryString [("user_id", Just . BS.pack . show $ i)]
        in setQuery $ HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users/extensions"
    toRequest ActiveExtensions = HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users/extensions"
    scope ActiveExtensions = Nothing
    scope (ActiveExtensionsFor _) = Nothing

data ActiveComponentExtensionEntry' = ActiveComponentExtensionEntry' { activeComponentActive' :: Bool
                                                                     , activeComponentExtensionId' :: String
                                                                     , activeComponentVersion' :: String
                                                                     , activeComponentName' :: String
                                                                     , activeComponentX :: Integer
                                                                     , activeComponentY :: Integer
                                                                     }
                                    | InactiveComponentExtension deriving ( Show, Eq )

instance FromJSON ActiveComponentExtensionEntry' where
    parseJSON = withObject "ActiveExtensionEntry" $ \o -> do
        activeComponentActive' <- o .: "active"
        if activeComponentActive' then do
            activeComponentExtensionId' <- o .: "id"
            activeComponentVersion' <- o .: "version"
            activeComponentName' <- o .: "name"
            activeComponentX <- o .: "x"
            activeComponentY <- o .: "y"
            return ActiveComponentExtensionEntry'{..}
        else return InactiveComponentExtension

data ActiveComponentExtensionEntry = ActiveComponentExtensionEntry { activeComponentExtensionActive :: Bool
                                                                   , activeComponentExtensionId :: String
                                                                   , activeComponentExtensionVersion :: String
                                                                   , activeComponentExtensionName :: String
                                                                   , x :: Integer
                                                                   , y :: Integer
                                                                   } deriving ( Show, Eq )

instance ExtensionId ActiveComponentExtensionEntry where
    extensionId = activeComponentExtensionId

instance IsActive ActiveComponentExtensionEntry where
    active = activeComponentExtensionActive

instance Named ActiveComponentExtensionEntry where
    name = activeComponentExtensionName

instance Versioned ActiveComponentExtensionEntry where
    version = activeComponentExtensionVersion

filterActiveComponentExtensions :: [ActiveComponentExtensionEntry'] -> [ActiveComponentExtensionEntry]
filterActiveComponentExtensions = reverse . foldl filterActiveComponentExtensions' []

filterActiveComponentExtensions' :: [ActiveComponentExtensionEntry] -> ActiveComponentExtensionEntry' -> [ActiveComponentExtensionEntry]
filterActiveComponentExtensions' as InactiveComponentExtension = as
filterActiveComponentExtensions' as (ActiveComponentExtensionEntry' _ i v n x y) = ActiveComponentExtensionEntry True i v n x y : as

data ActiveExtensionEntry' = ActiveExtensionEntry' { activeExtensionActive' :: Bool
                                                   , activeExtensionExtensionId' :: String
                                                   , activeExtensionVersion' :: String
                                                   , activeExtensionName' :: String
                                                   }
                          | InactiveExtension deriving ( Show, Eq )

instance FromJSON ActiveExtensionEntry' where
    parseJSON = withObject "ActiveExtensionEntry" $ \o -> do
        activeExtensionActive' <- o .: "active"
        if activeExtensionActive' then do
            activeExtensionExtensionId' <- o .: "id"
            activeExtensionVersion' <- o .: "version"
            activeExtensionName' <- o .: "name"
            return ActiveExtensionEntry'{..}
        else return InactiveExtension

data ActiveExtensionEntry = ActiveExtensionEntry { activeExtensionActive :: Bool
                                                 , activeExtensionId :: String
                                                 , activeExtensionVersion :: String
                                                 , activeExtensionName :: String
                                                 } deriving ( Show, Eq )

instance IsActive ActiveExtensionEntry where
    active = activeExtensionActive

instance ExtensionId ActiveExtensionEntry where
    extensionId = activeExtensionId

instance Versioned ActiveExtensionEntry where
    version = activeExtensionVersion

instance Named ActiveExtensionEntry where
    name = activeExtensionName

filterActiveExtensions :: [ActiveExtensionEntry'] -> [ActiveExtensionEntry]
filterActiveExtensions = reverse . foldl filterActiveExtensions' []

filterActiveExtensions' :: [ActiveExtensionEntry] -> ActiveExtensionEntry' -> [ActiveExtensionEntry]
filterActiveExtensions' as InactiveExtension = as
filterActiveExtensions' as (ActiveExtensionEntry' _ i v n) = ActiveExtensionEntry True i v n : as

data ActiveExtensionsResponse = ActiveExtensionsResponse { components :: [ActiveComponentExtensionEntry]
                                                         , overlays :: [ActiveExtensionEntry]
                                                         , panels :: [ActiveExtensionEntry]
                                                         } deriving ( Show, Eq )

instance FromJSON ActiveExtensionsResponse where
    parseJSON = withObject "ActiveExtensionsResponse" $ \o -> do
        components <- (((o .: "data") >>= (.: "component")) <&> ((snd <$>) . toAscList)) <&> filterActiveComponentExtensions
        overlays <- (((o .: "data") >>= (.: "overlay")) <&> ((snd <$>) . toAscList)) <&> filterActiveExtensions
        panels <- (((o .: "data") >>= (.: "panel")) <&> ((snd <$>) . toAscList)) <&> filterActiveExtensions
        return ActiveExtensionsResponse{..}