twitchapi 0.0.1 → 0.0.2
raw patch · 4 files changed
+544/−2 lines, 4 filesdep ~hoauth2
Dependency ranges changed: hoauth2
Files
- src/Web/TwitchAPI/Helix/Users.hs +305/−0
- test/Helix.hs +11/−0
- test/Helix/Users.hs +203/−0
- twitchapi.cabal +25/−2
+ src/Web/TwitchAPI/Helix/Users.hs view
@@ -0,0 +1,305 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# 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++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+ , displayName :: String+ , userId :: 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 userId = read userId' :: Integer+ createdAt = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 created+ userType = read userType' :: UserType+ broadcasterType = read broadcasterType' :: BroadcasterType++ description <- o .: "description"+ displayName <- o .: "display_name"+ login <- o .: "login"+ offlineImageURL <- o .: "offline_image_url"+ profileImageURL <- o .: "profile_image_url"+ email <- o .: "email"+ return UserEntry{..}++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+ , users :: [FollowEntry]+ , paginationCursor :: String+ } deriving ( Show, Eq )++instance FromJSON FollowsResponse where+ parseJSON = withObject "FollowsResponse" $ \o -> do+ total <- o .: "total"+ paginationCursor <- (o .: "pagination") >>= (.: "cursor")+ users <- 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 { userId :: Integer+ , userLogin :: String+ , displayName :: String+ } deriving ( Show, Eq )++instance FromJSON BlockListEntry where+ parseJSON = withObject "BlockListEntry" $ \o -> do+ userId' :: String <- o .: "user_id"+ let userId = read userId' :: Integer+ userLogin <- o .: "user_login"+ displayName <- o .: "display_name"+ return BlockListEntry{..}++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+ , extensionId :: String+ , name :: String+ , extensionTypes :: [ExtensionType]+ , version :: String+ } deriving ( Show, Eq )++instance FromJSON ExtensionsEntry where+ parseJSON = withObject "ExtensionsEntry" $ \o -> do+ extensionTypes' :: [String] <- o .: "type"+ let extensionTypes :: [ExtensionType] = read <$> extensionTypes'+ canActivate <- o .: "can_activate"+ extensionId <- o .: "id"+ name <- o .: "name"+ version <- 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{..}++newtype ActiveExtensions = ActiveExtensions { userID :: Maybe Integer } deriving ( Show, Eq )++instance Req.HelixRequest ActiveExtensions where+ toRequest user =+ let userID' :: [(BS.ByteString, Maybe BS.ByteString)] = maybe [] (\i -> [("user_id", Just . BS.pack . show $ i)]) (userID user)+ setQuery = HTTP.setQueryString userID'+ in setQuery $ HTTP.parseRequest_ "GET https://api.twitch.tv/helix/users/extensions"+ scope ActiveExtensions{} = Nothing++data ActiveComponentExtensionEntry' = ActiveComponentExtensionEntry' { active :: Bool+ , extensionId :: String+ , version :: String+ , name :: String+ , x :: Integer+ , y :: Integer+ }+ | InactiveComponentExtension deriving ( Show, Eq )++instance FromJSON ActiveComponentExtensionEntry' where+ parseJSON = withObject "ActiveExtensionEntry" $ \o -> do+ active <- o .: "active"+ if active then do+ extensionId <- o .: "id"+ version <- o .: "version"+ name <- o .: "name"+ x <- o .: "x"+ y <- o .: "y"+ return ActiveComponentExtensionEntry'{..}+ else return InactiveComponentExtension++data ActiveComponentExtensionEntry = ActiveComponentExtensionEntry { active :: Bool+ , extensionId :: String+ , version :: String+ , name :: String+ , x :: Integer+ , y :: Integer+ } deriving ( Show, Eq )++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' { active :: Bool+ , extensionId :: String+ , version :: String+ , name :: String+ } + | InactiveExtension deriving ( Show, Eq )++instance FromJSON ActiveExtensionEntry' where+ parseJSON = withObject "ActiveExtensionEntry" $ \o -> do+ active <- o .: "active"+ if active then do+ extensionId <- o .: "id"+ version <- o .: "version"+ name <- o .: "name"+ return ActiveExtensionEntry'{..}+ else return InactiveExtension++data ActiveExtensionEntry = ActiveExtensionEntry { active :: Bool+ , extensionId :: String+ , version :: String+ , name :: String+ } deriving ( Show, Eq )++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{..}
+ test/Helix.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE ScopedTypeVariables #-}++module Main ( main ) where++import qualified Helix.Users++import Test.QuickCheck+import Test.Hspec++main :: IO ()+main = Helix.Users.main
+ test/Helix/Users.hs view
@@ -0,0 +1,203 @@+{-# LANGUAGE ScopedTypeVariables #-}++module Helix.Users ( main ) where++import qualified Data.Aeson as JSON+import qualified Data.ByteString.Lazy as BS+import qualified Data.List as List+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Time as Time+import qualified Data.Time.RFC3339 as Time ( parseTimeRFC3339 )+import qualified Data.Time.Clock.POSIX as Time ( POSIXTime, posixSecondsToUTCTime )++import qualified Web.TwitchAPI.Helix.Users as Users++import Data.Char ( isDigit, isLetter )+import Text.Printf ( printf )++import Test.QuickCheck+import Test.Hspec++main :: IO ()+main = do+ hspec $ describe "Users Interactions (Twitch Fixtures)" $ do+ it "Parses Twitch's Single-User JSON Fixture" $ property prop_userJSONFixture+ it "Parses Twitch's Multi-User JSON Fixture" $ property prop_usersJSONFixture+ it "Parses Twitch's Follow JSON Fixture" $ property prop_followsJSONFixture+ it "Parses Twitch's Block List JSON Fixture" $ property prop_blocksJSONFixture+ it "Parses Twitch's Extensions List JSON Fixture" $ property prop_extensionsJSONFixture+ it "Parses Twitch's Active Extensions List JSON Fixture" $ property prop_activeExtensionsJSONFixture++prop_userJSONFixture :: Bool+prop_userJSONFixture =+ let broadcasterType = Users.Partner+ description = "Supporting third-party developers building Twitch integrations from chatbots to game integrations."+ displayName = "TwitchDev"+ userId = 141981764+ login = "twitchdev"+ offlineImageURL = "https://static-cdn.jtvnw.net/jtv_user_pictures/3f13ab61-ec78-4fe6-8481-8682cb3b0ac2-channel_offline_image-1920x1080.png"+ profileImageURL = "https://static-cdn.jtvnw.net/jtv_user_pictures/8a6381c7-d0c0-4576-b179-38bd5ce1d6af-profile_image-300x300.png"+ userType = Users.NormalUser+ email = Just "not-real@email.com"+ createdAt = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 "2016-12-14T20:32:28Z"+ record = Users.UserEntry broadcasterType description displayName userId login offlineImageURL profileImageURL userType email createdAt+ response = Right $ Users.UsersResponse [record]+ parsed = parseJSON singleUserJSON :: Either String Users.UsersResponse+ in response == parsed++prop_usersJSONFixture :: Bool+prop_usersJSONFixture =+ let broadcasterType = Users.Partner+ description = "Supporting third-party developers building Twitch integrations from chatbots to game integrations."+ displayName = "TwitchDev"+ userId = 141981764+ login = "twitchdev"+ offlineImageURL = "https://static-cdn.jtvnw.net/jtv_user_pictures/3f13ab61-ec78-4fe6-8481-8682cb3b0ac2-channel_offline_image-1920x1080.png"+ profileImageURL = "https://static-cdn.jtvnw.net/jtv_user_pictures/8a6381c7-d0c0-4576-b179-38bd5ce1d6af-profile_image-300x300.png"+ userType = Users.NormalUser+ email = Just "not-real@email.com"+ createdAt = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 "2016-12-14T20:32:28Z"+ record = Users.UserEntry broadcasterType description displayName userId login offlineImageURL profileImageURL userType email createdAt+ response = Right $ Users.UsersResponse [record, record]+ parsed = parseJSON multiUserJSON :: Either String Users.UsersResponse+ in response == parsed++prop_followsJSONFixture :: Bool+prop_followsJSONFixture =+ let a_from_id = 171003792+ a_from_login = "iiisutha067iii"+ a_from_name = "IIIsutha067III"+ a_to_id = 23161357+ a_to_name = "LIRIK"+ a_followed = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 "2017-08-22T22:55:24Z"++ b_from_id = 113627897+ b_from_login = "birdman616"+ b_from_name = "Birdman616"+ b_to_id = 23161357+ b_to_name = "LIRIK"+ b_followed = Time.zonedTimeToUTC <$> Time.parseTimeRFC3339 "2017-08-22T22:55:04Z"++ a = Users.FollowEntry a_from_id a_from_login a_from_name a_to_id a_to_name a_followed+ b = Users.FollowEntry b_from_id b_from_login b_from_name b_to_id b_to_name b_followed+ total = 12345+ cursor = "eyJiIjpudWxsLCJhIjoiMTUwMzQ0MTc3NjQyNDQyMjAwMCJ9"+ response = Right $ Users.FollowsResponse total [a, b] cursor+ parsed = parseJSON followsJSON :: Either String Users.FollowsResponse+ in response == parsed++prop_blocksJSONFixture :: Bool+prop_blocksJSONFixture =+ let a_user_id = 135093069+ a_user_login = "bluelava"+ a_display_name = "BlueLava"++ b_user_id = 27419011+ b_user_login = "travistyoj"+ b_display_name = "TravistyOJ"++ a = Users.BlockListEntry a_user_id a_user_login a_display_name+ b = Users.BlockListEntry b_user_id b_user_login b_display_name+ response = Right $ Users.BlockListResponse [a, b]+ parsed = parseJSON blocksJSON :: Either String Users.BlockListResponse+ in response == parsed++prop_extensionsJSONFixture :: Bool+prop_extensionsJSONFixture =+ let a_i = "wi08ebtatdc7oj83wtl9uxwz807l8b"+ a_v = "1.1.8"+ a_n = "Streamlabs Leaderboard"+ a_c = True+ a_t = [Users.Panel]++ b_i = "d4uvtfdr04uq6raoenvj7m86gdk16v"+ b_v = "2.0.2"+ b_n = "Prime Subscription and Loot Reminder"+ b_c = True+ b_t = [Users.Overlay]+++ c_i = "rh6jq1q334hqc2rr1qlzqbvwlfl3x0"+ c_v = "1.1.0"+ c_n = "TopClip"+ c_c = True+ c_t = [Users.Mobile, Users.Panel]++ d_i = "zfh2irvx2jb4s60f02jq0ajm8vwgka"+ d_v = "1.0.19"+ d_n = "Streamlabs"+ d_c = True+ d_t = [Users.Mobile, Users.Overlay]++ e_i = "lqnf3zxk0rv0g7gq92mtmnirjz2cjj"+ e_v = "0.0.1"+ e_n = "Dev Experience Test"+ e_c = True+ e_t = [Users.Component, Users.Mobile, Users.Panel, Users.Overlay]++ a = Users.ExtensionsEntry a_c a_i a_n a_t a_v+ b = Users.ExtensionsEntry b_c b_i b_n b_t b_v+ c = Users.ExtensionsEntry c_c c_i c_n c_t c_v+ d = Users.ExtensionsEntry d_c d_i d_n d_t d_v+ e = Users.ExtensionsEntry e_c e_i e_n e_t e_v+ response = Right $ Users.ExtensionsResponse [a, b, c, d, e]+ parsed = parseJSON extensionsJSON :: Either String Users.ExtensionsResponse+ in response == parsed++prop_activeExtensionsJSONFixture :: Bool+prop_activeExtensionsJSONFixture =+ let p_1_i = "rh6jq1q334hqc2rr1qlzqbvwlfl3x0"+ p_1_v = "1.1.0"+ p_1_n = "TopClip"++ p_2_i = "wi08ebtatdc7oj83wtl9uxwz807l8b"+ p_2_v = "1.1.8"+ p_2_n = "Streamlabs Leaderboard"++ p_3_i = "naty2zwfp7vecaivuve8ef1hohh6bo"+ p_3_v = "1.0.9"+ p_3_n = "Streamlabs Stream Schedule & Countdown"++ o_i = "zfh2irvx2jb4s60f02jq0ajm8vwgka"+ o_v = "1.0.19"+ o_n = "Streamlabs"++ c_i = "lqnf3zxk0rv0g7gq92mtmnirjz2cjj"+ c_v = "0.0.1"+ c_n = "Dev Experience Test"+ c_x = 0+ c_y = 0++ p = [ Users.ActiveExtensionEntry True p_1_i p_1_v p_1_n+ , Users.ActiveExtensionEntry True p_2_i p_2_v p_2_n+ , Users.ActiveExtensionEntry True p_3_i p_3_v p_3_n+ ]+ o = [ Users.ActiveExtensionEntry True o_i o_v o_n ]+ c = [ Users.ActiveComponentExtensionEntry True c_i c_v c_n c_x c_y ]+ response = Right $ Users.ActiveExtensionsResponse c o p+ parsed = parseJSON activeExtensionsJSON :: Either String Users.ActiveExtensionsResponse+ in response == parsed++parseJSON :: JSON.FromJSON a => String -> Either String a+parseJSON x = JSON.eitherDecode $ (BS.fromStrict . T.encodeUtf8 . T.pack) x++-- Server Responses++singleUserJSON :: String+singleUserJSON = "{\"data\": [{\"id\": \"141981764\",\"login\": \"twitchdev\",\"display_name\": \"TwitchDev\",\"type\": \"\",\"broadcaster_type\": \"partner\",\"description\": \"Supporting third-party developers building Twitch integrations from chatbots to game integrations.\",\"profile_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/8a6381c7-d0c0-4576-b179-38bd5ce1d6af-profile_image-300x300.png\",\"offline_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/3f13ab61-ec78-4fe6-8481-8682cb3b0ac2-channel_offline_image-1920x1080.png\",\"view_count\": 5980557,\"email\": \"not-real@email.com\",\"created_at\": \"2016-12-14T20:32:28Z\"}]}"++multiUserJSON :: String+multiUserJSON = "{\"data\": [{\"id\": \"141981764\",\"login\": \"twitchdev\",\"display_name\": \"TwitchDev\",\"type\": \"\",\"broadcaster_type\": \"partner\",\"description\": \"Supporting third-party developers building Twitch integrations from chatbots to game integrations.\",\"profile_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/8a6381c7-d0c0-4576-b179-38bd5ce1d6af-profile_image-300x300.png\",\"offline_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/3f13ab61-ec78-4fe6-8481-8682cb3b0ac2-channel_offline_image-1920x1080.png\",\"view_count\": 5980557,\"email\": \"not-real@email.com\",\"created_at\": \"2016-12-14T20:32:28Z\"}, {\"id\": \"141981764\",\"login\": \"twitchdev\",\"display_name\": \"TwitchDev\",\"type\": \"\",\"broadcaster_type\": \"partner\",\"description\": \"Supporting third-party developers building Twitch integrations from chatbots to game integrations.\",\"profile_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/8a6381c7-d0c0-4576-b179-38bd5ce1d6af-profile_image-300x300.png\",\"offline_image_url\": \"https://static-cdn.jtvnw.net/jtv_user_pictures/3f13ab61-ec78-4fe6-8481-8682cb3b0ac2-channel_offline_image-1920x1080.png\",\"view_count\": 5980557,\"email\": \"not-real@email.com\",\"created_at\": \"2016-12-14T20:32:28Z\"}]}"++followsJSON :: String+followsJSON = "{\"total\": 12345,\"data\":[{\"from_id\": \"171003792\",\"from_login\": \"iiisutha067iii\",\"from_name\": \"IIIsutha067III\",\"to_id\": \"23161357\",\"to_name\": \"LIRIK\",\"followed_at\": \"2017-08-22T22:55:24Z\"},{\"from_id\": \"113627897\",\"from_login\": \"birdman616\",\"from_name\": \"Birdman616\",\"to_id\": \"23161357\",\"to_name\": \"LIRIK\",\"followed_at\": \"2017-08-22T22:55:04Z\"}],\"pagination\":{\"cursor\": \"eyJiIjpudWxsLCJhIjoiMTUwMzQ0MTc3NjQyNDQyMjAwMCJ9\"}}"++blocksJSON :: String+blocksJSON = "{\"data\": [{\"user_id\": \"135093069\",\"user_login\": \"bluelava\",\"display_name\": \"BlueLava\"},{\"user_id\": \"27419011\",\"user_login\": \"travistyoj\",\"display_name\": \"TravistyOJ\"}]}"++extensionsJSON :: String+extensionsJSON = "{\"data\": [{\"id\": \"wi08ebtatdc7oj83wtl9uxwz807l8b\",\"version\": \"1.1.8\",\"name\": \"Streamlabs Leaderboard\",\"can_activate\": true,\"type\": [\"panel\"]},{\"id\": \"d4uvtfdr04uq6raoenvj7m86gdk16v\",\"version\": \"2.0.2\",\"name\": \"Prime Subscription and Loot Reminder\",\"can_activate\": true,\"type\": [\"overlay\"]},{\"id\": \"rh6jq1q334hqc2rr1qlzqbvwlfl3x0\",\"version\": \"1.1.0\",\"name\": \"TopClip\",\"can_activate\": true,\"type\": [\"mobile\",\"panel\"]},{\"id\": \"zfh2irvx2jb4s60f02jq0ajm8vwgka\",\"version\": \"1.0.19\",\"name\": \"Streamlabs\",\"can_activate\": true,\"type\": [\"mobile\",\"overlay\"]},{\"id\": \"lqnf3zxk0rv0g7gq92mtmnirjz2cjj\",\"version\": \"0.0.1\",\"name\": \"Dev Experience Test\",\"can_activate\": true,\"type\": [\"component\",\"mobile\",\"panel\",\"overlay\"]}]}"++activeExtensionsJSON :: String+activeExtensionsJSON = "{\"data\": {\"panel\": {\"1\": {\"active\": true,\"id\": \"rh6jq1q334hqc2rr1qlzqbvwlfl3x0\",\"version\": \"1.1.0\",\"name\": \"TopClip\"},\"2\": {\"active\": true,\"id\": \"wi08ebtatdc7oj83wtl9uxwz807l8b\",\"version\": \"1.1.8\",\"name\": \"Streamlabs Leaderboard\"},\"3\": {\"active\": true,\"id\": \"naty2zwfp7vecaivuve8ef1hohh6bo\",\"version\": \"1.0.9\",\"name\": \"Streamlabs Stream Schedule & Countdown\"}},\"overlay\": {\"1\": {\"active\": true,\"id\": \"zfh2irvx2jb4s60f02jq0ajm8vwgka\",\"version\": \"1.0.19\",\"name\": \"Streamlabs\"}},\"component\": {\"1\": {\"active\": true,\"id\": \"lqnf3zxk0rv0g7gq92mtmnirjz2cjj\",\"version\": \"0.0.1\",\"name\": \"Dev Experience Test\",\"x\": 0,\"y\": 0},\"2\": {\"active\": false}}}}"
twitchapi.cabal view
@@ -1,5 +1,5 @@ name: twitchapi-version: 0.0.1+version: 0.0.2 synopsis: Client access to Twitch.tv API endpoints description: Twitch.tv API client supporting Helix and PubSub homepage: https://github.com/wuest/haskell-twitchapi@@ -21,17 +21,20 @@ library ghc-options: -Wall -fwarn-implicit-prelude -fwarn-monomorphism-restriction -O2 -static default-language: Haskell2010+ default-extensions: LambdaCase+ , OverloadedStrings hs-source-dirs: src exposed-modules: Web.TwitchAPI.Helix.Bits , Web.TwitchAPI.Helix.ChannelPoints , Web.TwitchAPI.Helix.Request+ , Web.TwitchAPI.Helix.Users , Web.TwitchAPI.PubSub build-depends: base >= 4.11 && < 4.18 , aeson >= 2.1 && < 2.2 , bytestring >= 0.10 && < 0.12- , hoauth2 >= 1.16 && < 2.6+ , hoauth2 >= 1.16 && < 2.7 , http-client >= 0.5 && < 0.8 , text >= 1.2 && < 2.1 , time >= 1.6 && < 1.14@@ -54,3 +57,23 @@ , text >= 1.2 && < 2.1 , time >= 1.6 && < 1.14 , timerep >= 2.0 && < 2.2++test-suite helix+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ , src+ main-is: Helix.hs+ other-modules: Web.TwitchAPI.Helix.Request+ , Web.TwitchAPI.Helix.Users+ , Helix.Users+ build-depends: base >= 4.11 && < 4.18+ , QuickCheck >= 2.8+ , hspec >= 2.7+ , aeson >= 2.1 && < 2.2+ , bytestring >= 0.10 && < 0.12+ , text >= 1.2 && < 2.1+ , time >= 1.6 && < 1.14+ , timerep >= 2.0 && < 2.2+ , http-client >= 0.5 && < 0.8