Compare commits

...

14 Commits

Author SHA1 Message Date
539a6a04f1 Merge pull request '1.1.0' (#9) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 30s
Reviewed-on: #9
2026-01-16 09:56:07 +03:00
fc7cc89301 Маленькие правки и обновления README 2026-01-16 09:54:47 +03:00
ab84144990 Merge pull request '1.0.7' (#8) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 33s
Reviewed-on: #8
2025-12-19 23:25:02 +03:00
636ce6f914 Исправлен тип данных для обновления статуса награды 2025-12-19 23:24:38 +03:00
ffd6f9288e Merge pull request '1.0.6' (#7) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 35s
Reviewed-on: #7
2025-12-19 23:09:53 +03:00
7cbc08f35f Фикс типа награды 2025-12-19 23:09:23 +03:00
9af7c982eb Merge pull request '1.0.5' (#6) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 32s
Reviewed-on: #6
2025-12-17 21:31:28 +03:00
52ff7d4150 Исправлен ответ от Get Users 2025-12-17 21:23:37 +03:00
25c7c0fc51 Merge pull request '1.0.4' (#5) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 32s
Reviewed-on: #5
2025-12-17 21:09:58 +03:00
4a8ecdf158 Исправлен ответ от Get Streams 2025-12-17 21:09:34 +03:00
16bd8cabe1 Merge pull request '1.0.3' (#4) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 32s
Reviewed-on: #4
2025-12-17 21:00:49 +03:00
a8c21af06e Исправлен эндпоинт get_chatters и ошибку использования int в headers 2025-12-17 20:59:22 +03:00
3b2f97ddbd Merge pull request '1.0.2' (#3) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 32s
Reviewed-on: #3
2025-12-17 19:37:28 +03:00
dbb2c35504 Исправлен ответ от некоторых эндпоинтов 2025-12-17 19:37:06 +03:00
6 changed files with 269 additions and 104 deletions

View File

@ -1,18 +1,18 @@
repos:
- repo: https://github.com/crate-ci/typos
rev: v1.36.3
rev: v1.42.0
hooks:
- id: typos
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.13.2
rev: v0.14.13
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.405
rev: v1.1.408
hooks:
- id: pyright

170
README.md
View File

@ -0,0 +1,170 @@
# Twitch Client (Async)
An internal **async Twitch API client** built on top of **AioHTTPX**, providing typed access to the Twitch **Helix API** and **OAuth2** endpoints with built-in rate limiting and optional caching.
---
## What it provides
* `TwitchAPIClient` — async client for Twitch **Helix API**
* `TwitchAuthClient` — async client for Twitch **OAuth2**
* Built on **AioHTTPX** (aiohttp transport, Redis rate limiting & caching)
* Strongly typed responses using **Pydantic models**
* Automatic request parameter cleanup (`None` values removed)
---
## Installation (internal)
Configure your private package index, then:
```bash
pip install twitchclient
```
---
## Authentication
### OAuth helper client
```python
from twitchclient.auth import TwitchAuthClient
from twitchclient import scopes
auth = TwitchAuthClient(
client_id="CLIENT_ID",
client_secret="CLIENT_SECRET",
redirect_uri="https://your.app/callback",
)
url = await auth.create_authorization_code_grant_flow_url(
scope=[scopes.USER_READ_EMAIL, scopes.CHAT_READ],
)
```
### App access token
```python
token = await auth.app_access_token()
print(token.access_token)
```
### User access token
```python
token = await auth.user_access_token(code="AUTH_CODE")
```
---
## API Client
```python
from twitchclient.api import TwitchAPIClient
client = TwitchAPIClient(
client_id="CLIENT_ID",
client_secret="CLIENT_SECRET",
redirect_uri="https://your.app/callback",
redis_url="redis://localhost:6379", # optional
)
```
* Base URL: `https://api.twitch.tv/helix`
* `Client-Id` header is set automatically
* Rate limit defaults to **700 req/min** when Redis is enabled
---
## Example usage
### Get channel information
```python
channels = await client.get_channel_information(
access_token=token.access_token,
broadcaster_id=123456,
)
```
### Start a commercial
```python
await client.start_commercial(
access_token=token.access_token,
broadcaster_id=123456,
)
```
### Cached request
Caching is controlled via the `X-Cache-TTL` header:
```python
streams = await client.get_streams(
access_token=token.access_token,
game_id=509658,
cache_time=30,
)
```
---
## Rate limiting & caching
* **Redis-backed**
* Shared key: `twitch`
* Enabled automatically when `redis_url` is provided
* Cache TTL is per-request (`X-Cache-TTL` header)
Implementation is inherited from **AioHTTPX transports** .
---
## Errors
All API methods raise typed exceptions:
* `ClientError(status_code, message)`
* `InternalError(status_code, message)`
Defined in schema module .
---
## Scopes
OAuth scopes are defined as constants and typed literals:
```python
from twitchclient import scopes
scopes.CHANNEL_READ_SUBSCRIPTIONS
scopes.CHAT_READ
```
See `scopes.py` for the full list .
---
## Typed responses
All successful responses return **Pydantic models**, e.g.:
* `ChannelsInformation`
* `Streams`
* `Users`
* `EventsubSubscriptions`
Models live in `schema.py` .
---
## Notes & limitations
* Async-only (no sync client)
* Internal API, no stability guarantees
* Some endpoints still marked TODO (e.g. Guest Star)
---

View File

@ -1,16 +1,11 @@
[project]
name = "twitchclient"
version = "1.0.1"
version = "1.1.0"
description = "Client for Twitch API"
readme = "README.md"
authors = [
{ name = "Miwory", email = "miwory.uwu@gmail.com" }
]
authors = [{ name = "Miwory", email = "miwory.uwu@gmail.com" }]
requires-python = ">=3.13"
dependencies = [
"aiohttpx>=1.3,<=2.0",
"pydantic>=2.12,<=2.13",
]
dependencies = ["aiohttpx>=1.3,<=2.0", "pydantic>=2.12,<=2.13"]
[build-system]
requires = ["uv_build>=0.9.2,<0.10.0"]
@ -18,10 +13,10 @@ build-backend = "uv_build"
[project.optional-dependencies]
dev = [
"ruff==0.14.9",
"pyright==1.1.407",
"poethepoet==0.38.0",
"pre-commit==4.5.0",
"ruff==0.14.13",
"pyright==1.1.408",
"poethepoet==0.40.0",
"pre-commit==4.5.1",
]
[[tool.uv.index]]

View File

@ -13,10 +13,10 @@ from .eventsub import types as sub_type
class TwitchAPIClient(AioHTTPXClient):
def __init__(
self,
redis_url: str,
client_id: str,
client_secret: str,
redirect_uri: str,
redis_url: str | None = None,
):
self.base_uri = 'https://api.twitch.tv/helix'
self.client_id = client_id
@ -27,8 +27,8 @@ class TwitchAPIClient(AioHTTPXClient):
base_url=self.base_uri,
headers={'Client-Id': self.client_id},
redis_url=redis_url,
key='twitch',
limit=700,
key='twitch' if redis_url else None,
limit=700 if redis_url else None,
logger='Twitch API',
)
@ -67,7 +67,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.AdSchedule.model_validate(req.json()).data
return s.AdSchedule.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED:
raise s.ClientError(req.status_code, req.json()['message'])
@ -86,7 +86,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.SnoozeNextAd.model_validate(req.json()).data
return s.SnoozeNextAd.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.TOO_MANY_REQUESTS:
raise s.ClientError(req.status_code, req.json()['message'])
@ -122,7 +122,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.ExtensionAnalytics.model_validate(req.json()).data
return s.ExtensionAnalytics.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.NOT_FOUND:
raise s.ClientError(req.status_code, req.json()['message'])
@ -158,7 +158,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.GameAnalytics.model_validate(req.json()).data
return s.GameAnalytics.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.NOT_FOUND:
raise s.ClientError(req.status_code, req.json()['message'])
@ -190,7 +190,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.BitsLeaderboard.model_validate(req.json()).data
return s.BitsLeaderboard.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.FORBIDDEN:
raise s.ClientError(req.status_code, req.json()['message'])
@ -209,7 +209,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.Cheermotes.model_validate(req.json()).data
return s.Cheermotes.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED:
raise s.ClientError(req.status_code, req.json()['message'])
@ -241,7 +241,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.ExtensionTransactions.model_validate(req.json()).data
return s.ExtensionTransactions.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.NOT_FOUND:
raise s.ClientError(req.status_code, req.json()['message'])
@ -262,7 +262,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.ChannelsInformation.model_validate(req.json()).data
return s.ChannelsInformation.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.TOO_MANY_REQUESTS:
raise s.ClientError(req.status_code, req.json()['message'])
@ -328,7 +328,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.ChannelEditors.model_validate(req.json()).data
return s.ChannelEditors.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.TOO_MANY_REQUESTS:
raise s.ClientError(req.status_code, req.json()['message'])
@ -358,7 +358,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.FollowedChannels.model_validate(req.json()).data
return s.FollowedChannels.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.TOO_MANY_REQUESTS:
raise s.ClientError(req.status_code, req.json()['message'])
@ -390,7 +390,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.ChannelFollowers.model_validate(req.json()).data
return s.ChannelFollowers.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.TOO_MANY_REQUESTS:
raise s.ClientError(req.status_code, req.json()['message'])
@ -446,7 +446,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.CustomRewards.model_validate(req.json()).data
return s.CustomRewards.model_validate(req.json())
case (
st.BAD_REQUEST
@ -506,7 +506,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.CustomRewards.model_validate(req.json()).data
return s.CustomRewards.model_validate(req.json())
case (
st.BAD_REQUEST
@ -550,9 +550,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.CustomRewardRedemptions.model_validate(
req.json()
).data
return s.CustomRewardRedemptions.model_validate(req.json())
case (
st.BAD_REQUEST
@ -615,7 +613,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.CustomRewards.model_validate(req.json()).data
return s.CustomRewards.model_validate(req.json())
case (
st.BAD_REQUEST
@ -633,8 +631,8 @@ class TwitchAPIClient(AioHTTPXClient):
self,
access_token: str,
broadcaster_id: int,
reward_id: int,
redemption_id: int | list[int],
reward_id: str,
redemption_id: str | list[str],
status: Literal['CANCELED', 'FULFILLED'],
):
req = await self.post(
@ -675,7 +673,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.CharityCampaign.model_validate(req.json()).data
return s.CharityCampaign.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.FORBIDDEN:
raise s.ClientError(req.status_code, req.json()['message'])
@ -704,14 +702,14 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
)
match req.status_code:
case st.OK:
return s.CharityDonations.model_validate(req.json()).data
return s.CharityDonations.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.FORBIDDEN:
raise s.ClientError(req.status_code, req.json()['message'])
@ -730,7 +728,7 @@ class TwitchAPIClient(AioHTTPXClient):
cache_time: int | None = None,
):
req = await self.get(
'/chatters',
'/chat/chatters',
params=self.clean_dict(
{
'broadcaster_id': broadcaster_id,
@ -742,14 +740,14 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
)
match req.status_code:
case st.OK:
return s.Chatters.model_validate(req.json()).data
return s.Chatters.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.FORBIDDEN:
raise s.ClientError(req.status_code, req.json()['message'])
@ -769,7 +767,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'broadcaster_id': broadcaster_id},
@ -793,7 +791,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
)
@ -820,7 +818,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'emote_set_id': emote_set_id},
@ -848,7 +846,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'broadcaster_id': broadcaster_id},
@ -872,7 +870,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
)
@ -900,7 +898,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -913,7 +911,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.ChatSettings.model_validate(req.json()).data
return s.ChatSettings.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED:
raise s.ClientError(req.status_code, req.json()['message'])
@ -933,7 +931,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'broadcaster_id': broadcaster_id},
@ -941,7 +939,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.SharedChatSession.model_validate(req.json()).data
return s.SharedChatSession.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED:
raise s.ClientError(req.status_code, req.json()['message'])
@ -963,7 +961,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -1027,7 +1025,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.ChatSettings.model_validate(req.json()).data
return s.ChatSettings.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED:
raise s.ClientError(req.status_code, req.json()['message'])
@ -1123,7 +1121,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.Message.model_validate(req.json()).data
return s.Message.model_validate(req.json())
case (
st.BAD_REQUEST
@ -1148,7 +1146,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'user_id': user_id},
@ -1233,7 +1231,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -1253,7 +1251,7 @@ class TwitchAPIClient(AioHTTPXClient):
match req.status_code:
case st.OK:
return s.Clips.model_validate(req.json()).data
return s.Clips.model_validate(req.json())
case st.BAD_REQUEST | st.UNAUTHORIZED | st.NOT_FOUND:
raise s.ClientError(req.status_code, req.json()['message'])
@ -1275,7 +1273,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -1310,7 +1308,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
)
@ -1392,7 +1390,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -1500,7 +1498,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -1572,7 +1570,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {jwt_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -1705,7 +1703,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -1739,7 +1737,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {jwt_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'extension_id': extension_id},
@ -1816,7 +1814,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {jwt_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -1850,7 +1848,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -1883,7 +1881,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -2025,7 +2023,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -2063,7 +2061,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -2099,7 +2097,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -2133,7 +2131,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'broadcaster_id': broadcaster_id},
@ -2163,7 +2161,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'broadcaster_id': broadcaster_id},
@ -2191,7 +2189,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'broadcaster_id': broadcaster_id},
@ -2250,7 +2248,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={
@ -2511,7 +2509,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -2634,7 +2632,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -2671,7 +2669,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -2752,7 +2750,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -2869,7 +2867,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={
@ -2943,7 +2941,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -3047,7 +3045,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -3203,7 +3201,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -3241,7 +3239,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'broadcaster_id': broadcaster_id},
@ -3411,7 +3409,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -3444,7 +3442,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -3479,7 +3477,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'broadcaster_id': broadcaster_id},
@ -3514,7 +3512,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -3555,7 +3553,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -3625,7 +3623,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -3666,7 +3664,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -3702,7 +3700,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -3735,7 +3733,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'broadcaster_id': broadcaster_id},
@ -3764,7 +3762,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict({'name': name, 'id': team_id}),
@ -3793,7 +3791,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict({'id': user_id, 'login': login}),
@ -3842,7 +3840,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'user_id': user_id},
@ -3877,7 +3875,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(
@ -3962,7 +3960,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
)
@ -3989,7 +3987,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params={'user_id': user_id},
@ -4046,7 +4044,7 @@ class TwitchAPIClient(AioHTTPXClient):
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
'X-Cache-TTL': str(cache_time),
}
),
params=self.clean_dict(

View File

@ -10,10 +10,10 @@ from . import scopes
class TwitchAuthClient(AioHTTPXClient):
def __init__(
self,
redis_url: str,
client_id: str,
client_secret: str,
redirect_uri: str,
redis_url: str | None = None,
):
self.base_uri = 'https://id.twitch.tv/oauth2'
self.client_id = client_id
@ -23,8 +23,8 @@ class TwitchAuthClient(AioHTTPXClient):
super().__init__(
base_url=self.base_uri,
redis_url=redis_url,
key='twitch',
limit=700,
key='twitch' if redis_url else None,
limit=700 if redis_url else None,
logger='Twitch Auth',
)

View File

@ -309,7 +309,7 @@ class CustomReward(BaseSchema):
broadcaster_id: int
broadcaster_login: str
broadcaster_name: str
id: int
id: str
title: str
prompt: str
cost: int
@ -1263,6 +1263,7 @@ class Stream(BaseSchema):
type: Literal['live']
title: str
tags: list[str]
tag_ids: list[str] = Field(..., deprecated=True)
viewer_count: int
started_at: datetime
language: str
@ -1389,6 +1390,7 @@ class User(BaseSchema):
offline_image_url: str
email: str | None = None
created_at: datetime
view_count: int = Field(..., deprecated=True)
class Users(BaseSchema):