A tool to retrieve information from Twitch.
at main 199 lines 5.7 kB view raw
1"""Module with helper functions to interact with Twitch API.""" 2 3import logging 4import urllib 5 6import httpx 7 8from .settings import settings 9 10logger = logging.getLogger(__name__) 11 12 13# Common 14# -------------------------------------------------------------------------------------- 15 16 17def raise_for_status(response: httpx.Response) -> None: 18 """Unify the handle of errors in API requests.""" 19 if not response.is_success: 20 logger.error( 21 f"Error calling to {response.request.url}:\n" 22 f"{response.content.decode('utf-8')}" 23 ) 24 response.raise_for_status() 25 26 27# Twitch ID 28# -------------------------------------------------------------------------------------- 29 30 31def twitch_id_client() -> httpx.Client: 32 """Create a simple client to access Twitch ID API.""" 33 base_url = "https://id.twitch.tv" 34 return httpx.Client(base_url=base_url) 35 36 37def retrieve_token(code: str) -> dict: 38 """Retrieve the token information from Twitch using the given code.""" 39 logger.debug(f"Obtaining token information using code {code}") 40 params = { 41 "client_id": settings.client_id, 42 "client_secret": settings.client_secret, 43 "code": code, 44 "grant_type": "authorization_code", 45 "redirect_uri": settings.redirect_uri, 46 } 47 with twitch_id_client() as client: 48 response = client.post("/oauth2/token", params=params) 49 raise_for_status(response) 50 51 return response.json() 52 53 54def retrieve_authorize_url() -> str: 55 """Create and return the authorize URL.""" 56 authorize_url = "https://id.twitch.tv/oauth2/authorize" 57 params = { 58 "client_id": settings.client_id, 59 "redirect_uri": settings.redirect_uri, 60 "response_type": "code", 61 "scope": settings.scopes, 62 } 63 return f"{authorize_url}?{urllib.parse.urlencode(params)}" 64 65 66def validate_access_token(access_token: str) -> bool: 67 """Validate the given access token.""" 68 headers = {"Authorization": f"OAuth {access_token}"} 69 with twitch_id_client() as client: 70 response = client.get("/oauth2/validate", headers=headers) 71 return response.status_code == 200 72 73 74def refresh_access_token(refresh_token: str) -> dict: 75 """Refresh the access token.""" 76 params = { 77 "grant_type": "refresh_token", 78 "refresh_token": refresh_token, 79 "client_id": settings.client_id, 80 "client_secret": settings.client_secret, 81 } 82 83 with twitch_id_client() as client: 84 response = client.post("/oauth2/token", params=params) 85 raise_for_status(response) 86 87 data = response.json() 88 return data 89 90 91# Twitch API 92# -------------------------------------------------------------------------------------- 93 94 95def twitch_api_client(access_token: str) -> httpx.Client: 96 """Create a simple client to access Twitch API.""" 97 base_url = "https://api.twitch.tv" 98 headers = { 99 "Client-ID": settings.client_id, 100 "Authorization": f"Bearer {access_token}", 101 } 102 return httpx.Client(base_url=base_url, headers=headers) 103 104 105def retrieve_user(access_token: str) -> dict: 106 """Retrieve the user information.""" 107 with twitch_api_client(access_token) as client: 108 response = client.get("/helix/users") 109 raise_for_status(response) 110 111 data = response.json() 112 return data["data"][0] 113 114 115def retrieve_followed_channels(access_token: str, user_id: str) -> list[dict]: 116 """Retrieve the list of followed channels.""" 117 page_size = 100 118 params: dict[str, str | int] = { 119 "user_id": user_id, 120 "first": page_size, 121 } 122 123 followed: list[dict] = [] 124 total: int | None = None 125 126 while total is None or len(followed) > total: 127 with twitch_api_client(access_token) as client: 128 response = client.get("/helix/channels/followed", params=params) 129 raise_for_status(response) 130 data = response.json() 131 132 if total is None: 133 total = data["total"] 134 135 followed.extend(data["data"]) 136 137 cursor = data["pagination"].get("cursor") 138 if cursor: 139 params["after"] = cursor 140 141 return followed 142 143 144def retrieve_followed_streams(access_token: str, user_id: str) -> list[dict]: 145 """Retrieve the list of followed streams.""" 146 page_size = 100 147 params: dict[str, str | int] = { 148 "user_id": user_id, 149 "first": page_size, 150 } 151 152 streams: list[dict] = [] 153 first_query: bool = True 154 cursor: str | None = None 155 156 while first_query or cursor: 157 with twitch_api_client(access_token) as client: 158 response = client.get("/helix/streams/followed", params=params) 159 raise_for_status(response) 160 data = response.json() 161 162 if first_query: 163 first_query = False 164 165 streams.extend(data["data"]) 166 167 cursor = data["pagination"].get("cursor") 168 if cursor: 169 params["after"] = cursor 170 171 return streams 172 173 174def retrieve_live_streams( 175 access_token: str, 176 language: list[str] | None = None, 177 size: int | None = None, 178) -> list[dict]: 179 """Retrieve the list of live stream, sorted by viewers. 180 181 It doesn't relay in pagination, because the number of viewers can change between 182 calls, and therefore, it could generate duplicated results. 183 """ 184 params: dict[str, str | int | list[str]] = { 185 "type": "live", 186 } 187 if language: 188 params["language"] = language 189 if size: 190 if size > 100: 191 logger.warning("Twitch API supports maximum of 100 items.") 192 params["first"] = size 193 194 with twitch_api_client(access_token) as client: 195 response = client.get("/helix/streams", params=params) 196 raise_for_status(response) 197 data = response.json() 198 199 return data["data"]