packages feed

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 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