Альфа релиз
All checks were successful
Build And Publish Package / publish (push) Successful in 28s

This commit is contained in:
2025-12-11 15:56:56 +03:00
commit e437b41ee8
9 changed files with 1439 additions and 0 deletions

View File

@ -0,0 +1 @@
__version__ = '0.1.0'

747
src/twitchclient/api.py Normal file
View File

@ -0,0 +1,747 @@
from datetime import datetime
from typing import Literal
from aiohttpx import status as st
from aiohttpx.client import AioHTTPXClient
from . import schema as s
class TwitchAPIClient(AioHTTPXClient):
def __init__(
self,
redis_url: str,
client_id: str,
client_secret: str,
redirect_uri: str,
):
self.base_uri = 'https://api.twitch.tv/helix'
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
super().__init__(
base_url=self.base_uri,
headers={'Client-Id': self.client_id},
redis_url=redis_url,
key='twitch',
limit=10,
logger='Twitch API',
)
async def start_commercial(self, access_token: str, broadcaster_id: int):
req = await self.get(
'/channels/commercial',
headers={'Authorization': f'Bearer {access_token}'},
params={
'broadcaster_id': broadcaster_id,
},
)
match req.status_code:
case st.OK:
return s.StartCommercial.model_validate(req.json()).data
case (
st.BAD_REQUEST
| st.UNAUTHORIZED
| st.NOT_FOUND
| st.TOO_MANY_REQUESTS
):
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_ad_schedule(self, access_token: str, broadcaster_id: int):
req = await self.get(
'/channels/ads',
headers={'Authorization': f'Bearer {access_token}'},
params={
'broadcaster_id': broadcaster_id,
},
)
match req.status_code:
case st.OK:
return s.AdSchedule.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def snooze_next_ad(self, access_token: str, broadcaster_id: int):
req = await self.post(
'/channels/ads/schedule/snooze',
headers={'Authorization': f'Bearer {access_token}'},
params={
'broadcaster_id': broadcaster_id,
},
)
match req.status_code:
case st.OK:
return s.SnoozeNextAd.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.TOO_MANY_REQUESTS:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_extension_analytics(
self,
access_token: str,
extension_id: str | None = None,
analytics_type: Literal['overview_v2'] | None = None,
started_at: datetime | None = None,
ended_at: datetime | None = None,
first: int = 20,
after: str | None = None,
):
req = await self.get(
'/analytics/extensions',
headers={'Authorization': f'Bearer {access_token}'},
params=self.clean_dict(
{
'extension_id': extension_id,
'type': analytics_type,
'started_at': started_at,
'ended_at': ended_at,
'first': first,
'after': after,
}
),
)
match req.status_code:
case st.OK:
return s.ExtensionAnalytics.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.NOT_FOUND:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_game_analytics(
self,
access_token: str,
game_id: int | None = None,
analytics_type: Literal['overview_v2'] | None = None,
started_at: datetime | None = None,
ended_at: datetime | None = None,
first: int = 20,
after: str | None = None,
):
req = await self.get(
'/analytics/games',
headers={'Authorization': f'Bearer {access_token}'},
params=self.clean_dict(
{
'game_id': game_id,
'type': analytics_type,
'started_at': started_at,
'ended_at': ended_at,
'first': first,
'after': after,
}
),
)
match req.status_code:
case st.OK:
return s.GameAnalytics.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.NOT_FOUND:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_bits_leaderboard(
self,
access_token: str,
count: int = 10,
period: Literal['day', 'week', 'month', 'year', 'all'] = 'all',
started_at: datetime | None = None,
user_id: int | None = None,
):
req = await self.get(
'/bits/leaderboard',
headers={'Authorization': f'Bearer {access_token}'},
params=self.clean_dict(
{
'count': count,
'period': period,
'started_at': started_at,
'user_id': user_id,
}
),
)
match req.status_code:
case st.OK:
return s.BitsLeaderboard.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.FORBIDDEN:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_cheermotes(
self, access_token: str, broadcaster_id: int | None = None
):
req = await self.get(
'/bits/cheermotes',
headers={'Authorization': f'Bearer {access_token}'},
params=self.clean_dict({'broadcaster_id': broadcaster_id}),
)
match req.status_code:
case st.OK:
return s.Cheermotes.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_extension_transactions(
self,
access_token: str,
extension_id: str,
user_id: int | list[int] | None = None,
first: int = 20,
after: str | None = None,
):
req = await self.get(
'/extensions/transactions',
headers={'Authorization': f'Bearer {access_token}'},
params=self.clean_dict(
{
'extension_id': extension_id,
'user_id': user_id,
'first': first,
'after': after,
}
),
)
match req.status_code:
case st.OK:
return s.ExtensionTransactions.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.NOT_FOUND:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_channel_information(
self, access_token: str, broadcaster_id: int | list[int]
):
req = await self.get(
'/channels',
headers={'Authorization': f'Bearer {access_token}'},
params={'broadcaster_id': broadcaster_id},
)
match req.status_code:
case st.OK:
return s.ChannelsInformation.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.TOO_MANY_REQUESTS:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def modify_channel_information(
self,
access_token: str,
broadcaster_id: int,
*,
game_id: int | None = None,
broadcaster_language: str | None = None,
title: str | None = None,
delay: int | None = None,
tags: list[str] | None = None,
content_classification_labels: list[s.ContentClassificationLabel]
| None = None,
is_branded_content: bool | None = None,
):
data = self.clean_dict(
{
'game_id': game_id,
'broadcaster_language': broadcaster_language,
'title': title,
'delay': delay,
'tags': tags,
'content_classification_labels': content_classification_labels,
'is_branded_content': is_branded_content,
}
)
req = await self.patch(
'/channels',
headers={'Authorization': f'Bearer {access_token}'},
params={'broadcaster_id': broadcaster_id},
json=data,
)
match req.status_code:
case st.NO_CONTENT:
return True
case (
st.BAD_REQUEST
| st.UNAUTHORIZED
| st.FORBIDDEN
| st.TOO_MANY_REQUESTS
):
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_channel_editors(
self, access_token: str, broadcaster_id: int
):
req = await self.get(
'/channels/editors',
headers={'Authorization': f'Bearer {access_token}'},
params={'broadcaster_id': broadcaster_id},
)
match req.status_code:
case st.OK:
return s.ChannelEditors.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.TOO_MANY_REQUESTS:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_followed_channels(
self,
access_token: str,
broadcaster_id: int,
*,
first: int = 20,
after: str | None = None,
):
req = await self.get(
'/channels/followed',
headers={'Authorization': f'Bearer {access_token}'},
params=self.clean_dict(
{
'broadcaster_id': broadcaster_id,
'first': first,
'after': after,
}
),
)
match req.status_code:
case st.OK:
return s.FollowedChannels.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.TOO_MANY_REQUESTS:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_channel_followers(
self,
access_token: str,
broadcaster_id: int,
*,
user_id: int | None = None,
first: int = 20,
after: str | None = None,
):
req = await self.get(
'/channels/followers',
headers={'Authorization': f'Bearer {access_token}'},
params=self.clean_dict(
{
'broadcaster_id': broadcaster_id,
'user_id': user_id,
'first': first,
'after': after,
}
),
)
match req.status_code:
case st.OK:
return s.ChannelFollowers.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.TOO_MANY_REQUESTS:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def create_custom_rewards(
self,
access_token: str,
broadcaster_id: int,
title: str,
cost: int,
*,
prompt: str | None = None,
is_enabled: bool = True,
background_color: str | None = None,
is_user_input_required: bool = False,
is_max_per_stream_enabled: bool = False,
max_per_stream: int | None = None,
is_max_per_user_per_stream_enabled: bool = False,
max_per_user_per_stream: int | None = None,
is_global_cooldown_enabled: bool = False,
global_cooldown_seconds: int | None = None,
should_redemptions_skip_request_queue: bool = False,
):
data = self.clean_dict(
{
'broadcaster_id': broadcaster_id,
'title': title,
'cost': cost,
'prompt': prompt,
'is_enabled': is_enabled,
'background_color': background_color,
'is_user_input_required': is_user_input_required,
'is_max_per_stream_enabled': is_max_per_stream_enabled,
'max_per_stream': max_per_stream,
'is_max_per_user_per_'
'stream_enabled': is_max_per_user_per_stream_enabled,
'max_per_user_per_stream': max_per_user_per_stream,
'is_global_cooldown_enabled': is_global_cooldown_enabled,
'global_cooldown_seconds': global_cooldown_seconds,
'should_redemptions_skip_'
'request_queue': should_redemptions_skip_request_queue,
}
)
req = await self.post(
'/channel_points/custom_rewards',
headers={'Authorization': f'Bearer {access_token}'},
json=data,
)
match req.status_code:
case st.OK:
return s.CustomRewards.model_validate(req.json()).data
case (
st.BAD_REQUEST
| st.UNAUTHORIZED
| st.FORBIDDEN
| st.TOO_MANY_REQUESTS
):
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def delete_custom_reward(
self, access_token: str, broadcaster_id: str, reward_id: str
):
req = await self.delete(
'/channel_points/custom_rewards',
headers={'Authorization': f'Bearer {access_token}'},
params={'broadcaster_id': broadcaster_id, 'id': reward_id},
)
match req.status_code:
case st.NO_CONTENT:
return True
case (
st.BAD_REQUEST
| st.UNAUTHORIZED
| st.FORBIDDEN
| st.NOT_FOUND
| st.TOO_MANY_REQUESTS
):
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_custom_rewards(
self,
access_token: str,
broadcaster_id: int,
*,
reward_id: str | None = None,
only_manageable_rewards: bool = False,
):
req = await self.get(
'/channel_points/custom_rewards',
headers={'Authorization': f'Bearer {access_token}'},
params=self.clean_dict(
{
'broadcaster_id': broadcaster_id,
'id': reward_id,
'only_manageable_rewards': only_manageable_rewards,
}
),
)
match req.status_code:
case st.OK:
return s.CustomRewards.model_validate(req.json()).data
case (
st.BAD_REQUEST
| st.UNAUTHORIZED
| st.FORBIDDEN
| st.NOT_FOUND
| st.TOO_MANY_REQUESTS
):
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_custom_reward_redemption(
self,
access_token: str,
broadcaster_id: int,
reward_id: str,
status: Literal['CANCELED', 'FULFILLED', 'UNFULFILLED'],
*,
redemption_id: int | list[int] | None = None,
sort: Literal['OLDEST', 'NEWEST'] = 'OLDEST',
after: str | None = None,
first: int = 20,
):
req = await self.get(
'/channel_points/custom_rewards/redemptions',
headers={'Authorization': f'Bearer {access_token}'},
params=self.clean_dict(
{
'broadcaster_id': broadcaster_id,
'reward_id': reward_id,
'status': status,
'redemption_id': redemption_id,
'sort': sort,
'after': after,
'first': first,
}
),
)
match req.status_code:
case st.OK:
return s.CustomRewardRedemptions.model_validate(
req.json()
).data
case (
st.BAD_REQUEST
| st.UNAUTHORIZED
| st.NOT_FOUND
| st.TOO_MANY_REQUESTS
):
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def update_custom_reward(
self,
access_token: str,
broadcaster_id: int,
reward_id: str,
*,
title: str | None = None,
prompt: str | None = None,
cost: int | None = None,
background_color: str | None = None,
is_enabled: bool | None = None,
is_user_input_required: bool | None = None,
is_max_per_stream_enabled: bool | None = None,
max_per_stream: int | None = None,
is_max_per_user_per_stream_enabled: bool | None = None,
max_per_user_per_stream: int | None = None,
is_global_cooldown_enabled: bool | None = None,
global_cooldown_seconds: int | None = None,
is_paused: bool | None = None,
should_redemptions_skip_request_queue: bool | None = None,
):
data = self.clean_dict(
{
'title': title,
'prompt': prompt,
'cost': cost,
'background_color': background_color,
'is_enabled': is_enabled,
'is_user_input_required': is_user_input_required,
'is_max_per_stream_enabled': is_max_per_stream_enabled,
'max_per_stream': max_per_stream,
'is_max_per_user_per_'
'stream_enabled': is_max_per_user_per_stream_enabled,
'max_per_user_per_stream': max_per_user_per_stream,
'is_global_cooldown_enabled': is_global_cooldown_enabled,
'global_cooldown_seconds': global_cooldown_seconds,
'is_paused': is_paused,
'should_redemptions_skip_'
'request_queue': should_redemptions_skip_request_queue,
}
)
req = await self.patch(
'/channel_points/custom_rewards',
headers={'Authorization': f'Bearer {access_token}'},
params={'broadcaster_id': broadcaster_id, 'id': reward_id},
json=data,
)
match req.status_code:
case st.OK:
return s.CustomRewards.model_validate(req.json()).data
case (
st.BAD_REQUEST
| st.UNAUTHORIZED
| st.FORBIDDEN
| st.NOT_FOUND
| st.TOO_MANY_REQUESTS
):
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def update_redemption_status(
self,
access_token: str,
broadcaster_id: int,
reward_id: int,
redemption_id: int | list[int],
status: Literal['CANCELED', 'FULFILLED'],
):
req = await self.post(
'/channel_points/custom_rewards/redemptions',
headers={'Authorization': f'Bearer {access_token}'},
params={
'broadcaster_id': broadcaster_id,
'reward_id': reward_id,
'id': redemption_id,
},
json={'status': status},
)
match req.status_code:
case st.NO_CONTENT:
return True
case (
st.BAD_REQUEST
| st.UNAUTHORIZED
| st.FORBIDDEN
| st.NOT_FOUND
| st.TOO_MANY_REQUESTS
):
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_charity_campaign(
self, access_token: str, broadcaster_id: int
):
req = await self.get(
'/charity/campaigns',
params={'broadcaster_id': broadcaster_id},
headers={'Authorization': f'Bearer {access_token}'},
)
match req.status_code:
case st.OK:
return s.CharityCampaign.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.FORBIDDEN:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_charity_campaign_donations(
self,
access_token: str,
broadcaster_id: int,
first: int = 20,
after: str | None = None,
cache_time: int | None = None,
):
req = await self.get(
'/charity/donations',
params=self.clean_dict(
{
'broadcaster_id': broadcaster_id,
'first': first,
'after': after,
}
),
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
}
),
)
match req.status_code:
case st.OK:
return s.CharityDonations.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.FORBIDDEN:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')
async def get_chatters(
self,
access_token: str,
broadcaster_id: int,
moderator_id: int,
first: int = 20,
after: str | None = None,
cache_time: int | None = None,
):
req = await self.get(
'/chatters',
params=self.clean_dict(
{
'broadcaster_id': broadcaster_id,
'moderator_id': moderator_id,
'first': first,
'after': after,
}
),
headers=self.clean_dict(
{
'Authorization': f'Bearer {access_token}',
'X-Cache-TTL': cache_time,
}
),
)
match req.status_code:
case st.OK:
return s.Chatters.model_validate(req.json()).data
case st.BAD_REQUEST | st.UNAUTHORIZED | st.FORBIDDEN:
raise s.Error(req.status_code, req.json()['message'])
case _:
raise s.Error(req.status_code, 'Internal Server Error')

View File

477
src/twitchclient/schema.py Normal file
View File

@ -0,0 +1,477 @@
from datetime import datetime
from typing import Any, Literal, TypedDict
from pydantic import BaseModel, ConfigDict, Field
class Error(Exception):
status_code: int
error: str
def __init__(self, status_code: int, error: str) -> None:
self.status_code = status_code
self.error = error
super().__init__(f'{status_code}: {error}')
class Pagination(BaseModel):
model_config = ConfigDict(extra='forbid')
cursor: str
class StartCommercialData(BaseModel):
model_config = ConfigDict(extra='forbid')
length: int
message: str
retry_after: int
class StartCommercial(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[StartCommercialData]
class AdScheduleData(BaseModel):
model_config = ConfigDict(extra='forbid')
next_ad_at: datetime | None
last_ad_at: datetime | None
duration: int
preroll_free_time: int
snooze_count: int
snooze_refresh_at: datetime
class AdSchedule(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[AdScheduleData]
class SnoozeNextAdData(BaseModel):
model_config = ConfigDict(extra='forbid')
snooze_count: int
snooze_refresh_at: datetime
next_ad_at: datetime
class SnoozeNextAd(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[SnoozeNextAdData]
class DateRange(BaseModel):
model_config = ConfigDict(extra='forbid')
started_at: datetime
ended_at: datetime
class ExtensionAnalyticsData(BaseModel):
model_config = ConfigDict(extra='forbid')
extension_id: str
URL: str
type: str
date_range: DateRange
class ExtensionAnalytics(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[ExtensionAnalyticsData]
pagination: Pagination | dict[Any, Any] | None = None
class GameAnalyticsData(BaseModel):
model_config = ConfigDict(extra='forbid')
game_id: int
URL: str
type: str
date_range: DateRange
class GameAnalytics(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[GameAnalyticsData]
pagination: Pagination | dict[Any, Any] | None = None
class BitsLeaderboardData(BaseModel):
model_config = ConfigDict(extra='forbid')
user_id: int
user_login: str
user_name: str
rank: int
score: int
class BitsLeaderboard(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[BitsLeaderboardData]
date_range: DateRange
total: int
class CheermotesImageAnimated(BaseModel):
field_1: str = Field(..., alias='1')
field_1_5: str = Field(..., alias='1.5')
field_2: str = Field(..., alias='2')
field_3: str = Field(..., alias='3')
field_4: str = Field(..., alias='4')
class CheermotesImageStatic(BaseModel):
field_1: str = Field(..., alias='1')
field_1_5: str = Field(..., alias='1.5')
field_2: str = Field(..., alias='2')
field_3: str = Field(..., alias='3')
field_4: str = Field(..., alias='4')
class CheermotesImage(BaseModel):
animated: CheermotesImageAnimated
static: CheermotesImageStatic
class CheermotesImages(BaseModel):
model_config = ConfigDict(extra='forbid')
light: CheermotesImage
dark: CheermotesImage
class CheermotesTier(BaseModel):
model_config = ConfigDict(extra='forbid')
min_bits: int
id: Literal['1', '100', '500', '1000', '5000', '10000', '100000']
color: str
can_cheer: bool
show_in_bits_card: bool
images: CheermotesImages
class CheermotesData(BaseModel):
model_config = ConfigDict(extra='forbid')
prefix: str
tiers: list[CheermotesTier]
type: Literal[
'global_first_party',
'global_third_party',
'channel_custom',
'display_only',
'sponsored',
]
order: int
last_updated: datetime
is_charitable: bool
class Cheermotes(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[CheermotesData]
class ExtensionProductCost(BaseModel):
model_config = ConfigDict(extra='forbid')
amount: int
type: Literal['bits']
class ExtensionProductData(BaseModel):
model_config = ConfigDict(extra='forbid')
domain: str
sku: str
cost: ExtensionProductCost
inDevelopment: bool
displayName: str
expiration: str
broadcast: bool
class ExtensionTransactionsData(BaseModel):
model_config = ConfigDict(extra='forbid')
id: str
timestamp: datetime
broadcaster_id: int
broadcaster_login: str
broadcaster_name: str
user_id: int
user_login: str
user_name: str
product_type: Literal['BITS_IN_EXTENSION']
product_data: ExtensionProductData
class ExtensionTransactions(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[ExtensionTransactionsData]
pagination: Pagination | dict[Any, Any] | None = None
class ContentClassificationLabel(TypedDict):
id: Literal[
'DebatedSocialIssuesAndPolitics',
'DrugsIntoxication',
'SexualThemes',
'ViolentGraphic',
'Gambling',
'ProfanityVulgarity',
]
is_enabled: bool
class ChannelInformation(BaseModel):
model_config = ConfigDict(extra='forbid')
broadcaster_id: int
broadcaster_login: str
broadcaster_name: str
broadcaster_language: str
game_name: str
game_id: int | str
title: str
delay: int
tags: list[str]
content_classification_labels: list[str]
is_branded_content: bool
class ChannelsInformation(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[ChannelInformation]
class ChannelEditor(BaseModel):
model_config = ConfigDict(extra='forbid')
user_id: int
user_name: str
created_at: datetime
class ChannelEditors(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[ChannelEditor]
class FollowedChannel(BaseModel):
model_config = ConfigDict(extra='forbid')
broadcaster_id: int
broadcaster_login: str
broadcaster_name: str
followed_at: datetime
class FollowedChannels(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[FollowedChannel]
pagination: Pagination | dict[Any, Any] | None = None
total: int
class ChannelFollower(BaseModel):
model_config = ConfigDict(extra='forbid')
user_id: int
user_login: str
user_name: str
followed_at: datetime
class ChannelFollowers(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[ChannelFollower]
pagination: Pagination | dict[Any, Any] | None = None
total: int
class CustomRewardImage(BaseModel):
model_config = ConfigDict(extra='forbid')
url_1x: str
url_2x: str
url_4x: str
class MaxPerStreamSetting(BaseModel):
model_config = ConfigDict(extra='forbid')
is_enabled: bool
max_per_stream: int
class MaxPerUserPerStreamSetting(BaseModel):
model_config = ConfigDict(extra='forbid')
is_enabled: bool
max_per_user_per_stream: int
class GlobalCooldownSetting(BaseModel):
model_config = ConfigDict(extra='forbid')
is_enabled: bool
global_cooldown_seconds: int
class CustomReward(BaseModel):
model_config = ConfigDict(extra='forbid')
broadcaster_id: int
broadcaster_login: str
broadcaster_name: str
id: int
title: str
prompt: str
cost: int
is_paused: bool
is_in_stock: bool
background_color: str
is_enabled: bool
is_user_input_required: bool
should_redemptions_skip_request_queue: bool
redemptions_redeemed_current_stream: int | None
cooldown_expires_at: datetime | None
image: CustomRewardImage | None
default_image: CustomRewardImage
max_per_stream_setting: MaxPerStreamSetting
max_per_user_per_stream_setting: MaxPerUserPerStreamSetting
global_cooldown_setting: GlobalCooldownSetting
class CustomRewards(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[CustomReward]
class CustomRewardRedemptionReward(BaseModel):
model_config = ConfigDict(extra='forbid')
id: str
title: str
prompt: str
cost: int
class CustomRewardRedemption(BaseModel):
model_config = ConfigDict(extra='forbid')
id: int
broadcaster_id: int
broadcaster_login: str
broadcaster_name: str
user_id: int
user_login: str
user_name: str
user_input: str
status: Literal['CANCELED', 'FULFILLED', 'UNFULFILLED']
redeemed_at: datetime
reward: CustomRewardRedemptionReward
class CustomRewardRedemptions(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[CustomRewardRedemption]
class CharityCampaignCurrentAmount(BaseModel):
model_config = ConfigDict(extra='forbid')
amount: int
decimal_places: int
currency: str
class CharityCampaignTargetAmount(BaseModel):
model_config = ConfigDict(extra='forbid')
value: int
decimal_places: int
currency: str
class CharityCampaignData(BaseModel):
model_config = ConfigDict(extra='forbid')
id: str
broadcaster_id: int
broadcaster_login: str
broadcaster_name: str
charity_name: str
charity_description: str
charity_logo: str
charity_website: str
current_amount: CharityCampaignCurrentAmount
target_amount: CharityCampaignTargetAmount
class CharityCampaign(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[CharityCampaignData]
class CharityDonationAmount(BaseModel):
model_config = ConfigDict(extra='forbid')
value: int
decimal_places: int
currency: str
class CharityDonation(BaseModel):
model_config = ConfigDict(extra='forbid')
id: str
campaign_id: str
user_id: int
user_login: str
user_name: str
amount: CharityDonationAmount
class CharityDonations(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[CharityDonation]
pagination: Pagination | dict[Any, Any] | None = None
class ChattersData(BaseModel):
model_config = ConfigDict(extra='forbid')
user_id: int
user_login: str
user_name: str
class Chatters(BaseModel):
model_config = ConfigDict(extra='forbid')
data: list[ChattersData]
pagination: Pagination | dict[Any, Any] | None = None
total: int