Compare commits

...

32 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
1c403da75b Merge pull request '1.0.1' (#2) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 34s
Reviewed-on: #2
2025-12-17 18:40:04 +03:00
899223dc56 Фикс типа Any скоупов 2025-12-17 18:39:37 +03:00
1b8e8c9a79 Merge pull request '1.0.0: Релиз' (#1) from dev into latest
All checks were successful
Build And Publish Package / publish (push) Successful in 32s
Reviewed-on: #1
2025-12-17 06:31:41 +03:00
fbc628bc3b Релиз 2025-12-17 06:30:13 +03:00
bb99154131 Добавлены update_user_chat_color, create_clip, get_clips 2025-12-11 19:06:43 +03:00
b279a6c977 Добавлены send_shoutout, send_chat_message, get_user_chat_color 2025-12-11 17:30:02 +03:00
e6c99a676f Добавлен send_chat_announcement 2025-12-11 17:22:40 +03:00
9d80ad7d69 Добавлен get_user_emotes 2025-12-11 17:14:35 +03:00
a95ddc36d4 Добавлено get_shared_chat_session 2025-12-11 17:10:29 +03:00
f04916b7fd Добавлен get_chat_settings 2025-12-11 16:34:35 +03:00
0d9ac981b8 Добавлен get_global_chat_badges 2025-12-11 16:24:43 +03:00
035a87e0c5 Упрощена типизация для broadcaster_id 2025-12-11 16:24:00 +03:00
3c6154913a Добавлен get_channel_chat_badges 2025-12-11 16:21:55 +03:00
33d498015b Добавлен get_emote_sets 2025-12-11 16:18:28 +03:00
b0d3ca9add Добавлен get_global_emotes 2025-12-11 16:16:30 +03:00
1a0999f332 Обновление версии 2025-12-11 16:15:03 +03:00
2c5c650ae7 Добавлен cache_time для get_channel_emotes 2025-12-11 16:14:43 +03:00
947f19afbc Добавлен эндпоинт get_channel_emotes 2025-12-11 16:13:06 +03:00
13 changed files with 5997 additions and 237 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 = "0.1.0"
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.2",
"pyright==1.1.406",
"poethepoet==0.37.0",
"pre-commit==4.3.0",
"ruff==0.14.13",
"pyright==1.1.408",
"poethepoet==0.40.0",
"pre-commit==4.5.1",
]
[[tool.uv.index]]
@ -41,6 +36,10 @@ _lint = "pre-commit run --all-files"
lint = ["_git", "_lint"]
check = "uv pip ls --outdated"
major = "uv version --bump major"
minor = "uv version --bump minor"
patch = "uv version --bump patch"
[tool.pyright]
venvPath = "."
venv = ".venv"
@ -50,6 +49,7 @@ strictSetInference = true
deprecateTypingAliases = true
typeCheckingMode = "strict"
pythonPlatform = "All"
stubPath = "typings"
[tool.ruff]
target-version = "py313"

File diff suppressed because it is too large Load Diff

126
src/twitchclient/auth.py Normal file
View File

@ -0,0 +1,126 @@
from urllib.parse import urlencode
from aiohttpx import status as st
from aiohttpx.client import AioHTTPXClient
from . import schema as s
from . import scopes
class TwitchAuthClient(AioHTTPXClient):
def __init__(
self,
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
self.client_secret = client_secret
self.redirect_uri = redirect_uri
super().__init__(
base_url=self.base_uri,
redis_url=redis_url,
key='twitch' if redis_url else None,
limit=700 if redis_url else None,
logger='Twitch Auth',
)
async def create_authorization_code_grant_flow_url(
self,
scope: list[scopes.Any],
*,
force_verify: bool = False,
state: str | None = None,
):
url = 'https://id.twitch.tv/oauth2/authorize?'
query = self.clean_dict(
{
'client_id': self.client_id,
'redirect_uri': self.redirect_uri,
'response_type': 'code',
'scope': ' '.join(scope),
'state': state,
'force_verify': force_verify,
}
)
return url + urlencode(query)
async def app_access_token(self):
req = await self.post(
'/token',
json={
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'client_credentials',
},
)
match req.status_code:
case st.OK:
return s.AppAccessToken.model_validate(req.json())
case _:
raise s.InternalError(req.status_code, 'Internal Server Error')
async def user_access_token(self, code: str):
req = await self.post(
'/token',
json={
'client_id': self.client_id,
'client_secret': self.client_secret,
'redirect_uri': self.redirect_uri,
'grant_type': 'authorization_code',
'code': code,
},
)
match req.status_code:
case st.OK:
return s.UserAccessToken.model_validate(req.json())
case st.BAD_REQUEST:
return None
case _:
raise s.InternalError(req.status_code, 'Internal Server Error')
async def validate_access_token(self, access_token: str):
req = await self.get(
'/validate',
headers={'Authorization': f'OAuth {access_token}'},
)
match req.status_code:
case st.OK:
return s.AccessTokenValidation.model_validate(req.json())
case st.UNAUTHORIZED:
return None
case _:
raise s.InternalError(req.status_code, 'Internal Server Error')
async def refresh_access_token(self, refresh_token: str):
req = await self.post(
'/token',
json={
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'refresh_token',
'refresh_token': refresh_token,
},
)
match req.status_code:
case st.OK:
return s.UserAccessToken.model_validate(req.json())
case st.BAD_REQUEST:
return None
case _:
raise s.InternalError(req.status_code, 'Internal Server Error')

View File

View File

@ -0,0 +1,51 @@
from pydantic import BaseModel
class User(BaseModel):
user_id: str
class Broadcaster(BaseModel):
broadcaster_user_id: str
class ToBroadcaster(BaseModel):
to_broadcaster_user_id: str
class FromBroadcaster(BaseModel):
from_broadcaster_user_id: str
class BroadcasterUser(Broadcaster, User):
pass
class BroadcasterModerator(Broadcaster):
moderator_user_id: str
class ClientID(BaseModel):
client_id: str
class Organization(BaseModel):
organization_id: int
category_id: int | None = None
campaign_id: int | None = None
class ExtensionClientID(BaseModel):
extension_client_id: str
Any = (
Broadcaster
| ToBroadcaster
| FromBroadcaster
| BroadcasterUser
| BroadcasterModerator
| ClientID
| Organization
| ExtensionClientID
)

View File

@ -0,0 +1,43 @@
from typing import Literal
ENABLED = Literal['enabled']
PENDING = Literal['webhook_callback_verification_pending']
FAILED = Literal['webhook_callback_verification_failed']
EXCEEDED = Literal['notification_failures_exceeded']
REVOKED = Literal['authorization_revoked']
MODERATOR_REMOVED = Literal['moderator_removed']
USER_REMOVED = Literal['user_removed']
CHAT_USER_BANNED = Literal['chat_user_banned']
VERSION_REMOVED = Literal['version_removed']
BETA_MAINTENANCE = Literal['beta_maintenance']
WEBSOCKET_DISCONNECTED = Literal['websocket_disconnected']
WEBSOCKET_FAILED_PING_PONG = Literal['websocket_failed_ping_pong']
WEBSOCKET_RECEIVED_INBOUND_TRAFFIC = Literal[
'websocket_received_inbound_traffic'
]
WEBSOCKET_CONNECTION_UNUSED = Literal['websocket_connection_unused']
WEBSOCKET_INTERNAL_ERROR = Literal['websocket_internal_error']
WEBSOCKET_NETWORK_TIMEOUT = Literal['websocket_network_timeout']
WEBSOCKET_NETWORK_ERROR = Literal['websocket_network_error']
WEBSOCKET_FAILED_TO_RECONNECT = Literal['websocket_failed_to_reconnect']
Any = (
ENABLED
| PENDING
| FAILED
| EXCEEDED
| REVOKED
| MODERATOR_REMOVED
| USER_REMOVED
| CHAT_USER_BANNED
| VERSION_REMOVED
| BETA_MAINTENANCE
| WEBSOCKET_DISCONNECTED
| WEBSOCKET_FAILED_PING_PONG
| WEBSOCKET_RECEIVED_INBOUND_TRAFFIC
| WEBSOCKET_CONNECTION_UNUSED
| WEBSOCKET_INTERNAL_ERROR
| WEBSOCKET_NETWORK_TIMEOUT
| WEBSOCKET_NETWORK_ERROR
| WEBSOCKET_FAILED_TO_RECONNECT
)

View File

@ -0,0 +1,564 @@
from datetime import datetime
from pydantic import BaseModel
from . import conditions as c
from . import statuses as s
from . import transports as t
from . import types as st
class BaseSubscription(BaseModel):
id: str
version: int
status: s.Any
cost: int
created_at: datetime
transport: t.Any
default_version: int = 1
class AutomodMessageHold(BaseSubscription):
type: st.AUTOMOD_MESSAGE_HOLD
condition: c.BroadcasterModerator
default_version: int = 2
class AutomodMessageUpdate(BaseSubscription):
type: st.AUTOMOD_MESSAGE_UPDATE
condition: c.BroadcasterModerator
default_version: int = 2
class AutomodSettingsUpdate(BaseSubscription):
type: st.AUTOMOD_SETTINGS_UPDATE
condition: c.BroadcasterModerator
default_version: int = 1
class AutomodTermsUpdate(BaseSubscription):
type: st.AUTOMOD_TERMS_UPDATE
condition: c.BroadcasterModerator
default_version: int = 1
class ChannelBitsUse(BaseSubscription):
type: st.CHANNEL_BITS_USE
condition: c.Broadcaster
default_version: int = 1
class ChannelUpdate(BaseSubscription):
type: st.CHANNEL_UPDATE
condition: c.Broadcaster
default_version: int = 2
class ChannelFollow(BaseSubscription):
type: st.CHANNEL_FOLLOW
condition: c.BroadcasterModerator
default_version: int = 2
class ChannelAdBreakBegin(BaseSubscription):
type: st.CHANNEL_AD_BREAK_BEGIN
condition: c.Broadcaster
default_version: int = 1
class ChannelChatClear(BaseSubscription):
type: st.CHANNEL_CHAT_CLEAR
condition: c.BroadcasterUser
default_version: int = 1
class ChannelChatClearUserMessages(BaseSubscription):
type: st.CHANNEL_CHAT_CLEAR_USER_MESSAGES
condition: c.BroadcasterUser
default_version: int = 1
class ChannelChatMessage(BaseSubscription):
type: st.CHANNEL_CHAT_MESSAGE
condition: c.BroadcasterUser
default_version: int = 1
class ChannelChatMessageDelete(BaseSubscription):
type: st.CHANNEL_CHAT_MESSAGE_DELETE
condition: c.BroadcasterUser
default_version: int = 1
class ChannelChatNotification(BaseSubscription):
type: st.CHANNEL_CHAT_NOTIFICATION
condition: c.BroadcasterUser
default_version: int = 1
class ChannelChatSettingsUpdate(BaseSubscription):
type: st.CHANNEL_CHAT_SETTINGS_UPDATE
condition: c.BroadcasterUser
default_version: int = 1
class ChannelChatUserMessageHold(BaseSubscription):
type: st.CHANNEL_CHAT_USER_MESSAGE_HOLD
condition: c.BroadcasterUser
default_version: int = 1
class ChannelChatUserMessageUpdate(BaseSubscription):
type: st.CHANNEL_CHAT_USER_MESSAGE_UPDATE
condition: c.BroadcasterUser
default_version: int = 1
class ChannelSharedChatSessionBegin(BaseSubscription):
type: st.CHANNEL_SHARED_CHAT_SESSION_BEGIN
condition: c.Broadcaster
default_version: int = 1
class ChannelSharedChatSessionUpdate(BaseSubscription):
type: st.CHANNEL_SHARED_CHAT_SESSION_UPDATE
condition: c.Broadcaster
default_version: int = 1
class ChannelSharedChatSessionEnd(BaseSubscription):
type: st.CHANNEL_SHARED_CHAT_SESSION_END
condition: c.Broadcaster
default_version: int = 1
class ChannelSubscribe(BaseSubscription):
type: st.CHANNEL_SUBSCRIBE
condition: c.Broadcaster
default_version: int = 1
class ChannelSubscriptionEnd(BaseSubscription):
type: st.CHANNEL_SUBSCRIPTION_END
condition: c.Broadcaster
default_version: int = 1
class ChannelSubscriptionGift(BaseSubscription):
type: st.CHANNEL_SUBSCRIPTION_GIFT
condition: c.Broadcaster
default_version: int = 1
class ChannelSubscriptionMessage(BaseSubscription):
type: st.CHANNEL_SUBSCRIPTION_MESSAGE
condition: c.Broadcaster
default_version: int = 1
class ChannelCheer(BaseSubscription):
type: st.CHANNEL_CHEER
condition: c.Broadcaster
default_version: int = 1
class ChannelRaid(BaseSubscription):
type: st.CHANNEL_RAID
condition: c.ToBroadcaster
default_version: int = 1
class ChannelBan(BaseSubscription):
type: st.CHANNEL_BAN
condition: c.Broadcaster
default_version: int = 1
class ChannelUnban(BaseSubscription):
type: st.CHANNEL_UNBAN
condition: c.Broadcaster
default_version: int = 1
class ChannelUnbanRequestCreate(BaseSubscription):
type: st.CHANNEL_UNBAN_REQUEST_CREATE
condition: c.BroadcasterModerator
default_version: int = 1
class ChannelUnbanRequestResolve(BaseSubscription):
type: st.CHANNEL_UNBAN_REQUEST_RESOLVE
condition: c.BroadcasterModerator
default_version: int = 1
class ChannelModerate(BaseSubscription):
type: st.CHANNEL_MODERATE
condition: c.BroadcasterModerator
default_version: int = 2
class ChannelModeratorAdd(BaseSubscription):
type: st.CHANNEL_MODERATOR_ADD
condition: c.Broadcaster
default_version: int = 1
class ChannelModeratorRemove(BaseSubscription):
type: st.CHANNEL_MODERATOR_REMOVE
condition: c.Broadcaster
default_version: int = 1
# TODO: Complete when subscription version is released
# class ChannelGuestStarSessionBegin(BaseSubscription):
# type: st.CHANNEL_GUEST_STAR_SESSION_BEGIN
# condition: c.Broadcaster
# default_version: int = 1
# TODO: Complete when subscription version is released
# class ChannelGuestStarSessionEnd(BaseSubscription):
# type: st.CHANNEL_GUEST_STAR_SESSION_END
# condition: c.Broadcaster
# default_version: int = 1
# TODO: Complete when subscription version is released
# class ChannelGuestStarGuestUpdate(BaseSubscription):
# type: st.CHANNEL_GUEST_STAR_GUEST_UPDATE
# condition: c.Broadcaster
# default_version: int = 1
# TODO: Complete when subscription version is released
# class ChannelGuestStarSettingsUpdate(BaseSubscription):
# type: st.CHANNEL_GUEST_STAR_SETTINGS_UPDATE
# condition: c.Broadcaster
# default_version: int = 1
class ChannelPointsAutomaticRewardRedemptionAdd(BaseSubscription):
type: st.CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD
condition: c.Broadcaster
default_version: int = 2
class ChannelPointsCustomRewardAdd(BaseSubscription):
type: st.CHANNEL_POINTS_CUSTOM_REWARD_ADD
condition: c.Broadcaster
default_version: int = 1
class ChannelPointsCustomRewardUpdate(BaseSubscription):
type: st.CHANNEL_POINTS_CUSTOM_REWARD_UPDATE
condition: c.Broadcaster
default_version: int = 1
class ChannelPointsCustomRewardRemove(BaseSubscription):
type: st.CHANNEL_POINTS_CUSTOM_REWARD_REMOVE
condition: c.Broadcaster
default_version: int = 1
class ChannelPointsCustomRewardRedemptionAdd(BaseSubscription):
type: st.CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD
condition: c.Broadcaster
default_version: int = 1
class ChannelPointsCustomRewardRedemptionUpdate(BaseSubscription):
type: st.CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE
condition: c.Broadcaster
default_version: int = 1
class ChannelPollBegin(BaseSubscription):
type: st.CHANNEL_POLL_BEGIN
condition: c.Broadcaster
default_version: int = 1
class ChannelPollProgress(BaseSubscription):
type: st.CHANNEL_POLL_PROGRESS
condition: c.Broadcaster
default_version: int = 1
class ChannelPollEnd(BaseSubscription):
type: st.CHANNEL_POLL_END
condition: c.Broadcaster
default_version: int = 1
class ChannelPredictionBegin(BaseSubscription):
type: st.CHANNEL_PREDICTION_BEGIN
condition: c.Broadcaster
default_version: int = 1
class ChannelPredictionProgress(BaseSubscription):
type: st.CHANNEL_PREDICTION_PROGRESS
condition: c.Broadcaster
default_version: int = 1
class ChannelPredictionLock(BaseSubscription):
type: st.CHANNEL_PREDICTION_LOCK
condition: c.Broadcaster
default_version: int = 1
class ChannelPredictionEnd(BaseSubscription):
type: st.CHANNEL_PREDICTION_END
condition: c.Broadcaster
default_version: int = 1
class ChannelSuspiciousUserMessage(BaseSubscription):
type: st.CHANNEL_SUSPICIOUS_USER_MESSAGE
condition: c.BroadcasterModerator
default_version: int = 1
class ChannelSuspiciousUserUpdate(BaseSubscription):
type: st.CHANNEL_SUSPICIOUS_USER_UPDATE
condition: c.BroadcasterModerator
default_version: int = 1
class ChannelVIPAdd(BaseSubscription):
type: st.CHANNEL_VIP_ADD
condition: c.Broadcaster
default_version: int = 1
class ChannelVIPRemove(BaseSubscription):
type: st.CHANNEL_VIP_REMOVE
condition: c.Broadcaster
default_version: int = 1
class ChannelWarningAcknowledgement(BaseSubscription):
type: st.CHANNEL_WARNING_ACKNOWLEDGEMENT
condition: c.BroadcasterModerator
default_version: int = 1
class ChannelWarningSend(BaseSubscription):
type: st.CHANNEL_WARNING_SEND
condition: c.BroadcasterModerator
default_version: int = 1
class CharityDonation(BaseSubscription):
type: st.CHARITY_DONATION
condition: c.Broadcaster
default_version: int = 1
class CharityCampaignStart(BaseSubscription):
type: st.CHARITY_CAMPAIGN_START
condition: c.Broadcaster
default_version: int = 1
class CharityCampaignProgress(BaseSubscription):
type: st.CHARITY_CAMPAIGN_PROGRESS
condition: c.Broadcaster
default_version: int = 1
class CharityCampaignStop(BaseSubscription):
type: st.CHARITY_CAMPAIGN_STOP
condition: c.Broadcaster
default_version: int = 1
class ConduitShardDisabled(BaseSubscription):
type: st.CONDUIT_SHARD_DISABLED
condition: c.ClientID
default_version: int = 1
class DropEntitlementGrant(BaseSubscription):
type: st.DROP_ENTITLEMENT_GRANT
condition: c.Organization
default_version: int = 1
class ExtensionBitsTransactionCreate(BaseSubscription):
type: st.EXTENSION_BITS_TRANSACTION_CREATE
condition: c.ExtensionClientID
default_version: int = 1
class GoalBegin(BaseSubscription):
type: st.GOAL_BEGIN
condition: c.Broadcaster
default_version: int = 1
class GoalProgress(BaseSubscription):
type: st.GOAL_PROGRESS
condition: c.Broadcaster
default_version: int = 1
class GoalEnd(BaseSubscription):
type: st.GOAL_END
condition: c.Broadcaster
default_version: int = 1
class HypeTrainBegin(BaseSubscription):
type: st.HYPE_TRAIN_BEGIN
condition: c.Broadcaster
default_version: int = 2
class HypeTrainProgress(BaseSubscription):
type: st.HYPE_TRAIN_PROGRESS
condition: c.Broadcaster
default_version: int = 2
class HypeTrainEnd(BaseSubscription):
type: st.HYPE_TRAIN_END
condition: c.Broadcaster
default_version: int = 2
class ShieldModeBegin(BaseSubscription):
type: st.SHIELD_MODE_BEGIN
condition: c.BroadcasterModerator
default_version: int = 1
class ShieldModeEnd(BaseSubscription):
type: st.SHIELD_MODE_END
condition: c.BroadcasterModerator
default_version: int = 1
class ShoutoutCreate(BaseSubscription):
type: st.SHOUTOUT_CREATE
condition: c.BroadcasterModerator
default_version: int = 1
class ShoutoutReceived(BaseSubscription):
type: st.SHOUTOUT_RECEIVED
condition: c.BroadcasterModerator
default_version: int = 1
class StreamOnline(BaseSubscription):
type: st.STREAM_ONLINE
condition: c.Broadcaster
default_version: int = 1
class StreamOffline(BaseSubscription):
type: st.STREAM_OFFLINE
condition: c.Broadcaster
default_version: int = 1
class UserAuthorizationGrant(BaseSubscription):
type: st.USER_AUTHORIZATION_GRANT
condition: c.ClientID
default_version: int = 1
class UserAuthorizationRevoke(BaseSubscription):
type: st.USER_AUTHORIZATION_REVOKE
condition: c.ClientID
default_version: int = 1
class UserUpdate(BaseSubscription):
type: st.USER_UPDATE
condition: c.User
default_version: int = 1
class WhisperReceived(BaseSubscription):
type: st.WHISPER_RECEIVED
condition: c.User
default_version: int = 1
Any = (
AutomodMessageHold
| AutomodMessageUpdate
| AutomodSettingsUpdate
| AutomodTermsUpdate
| ChannelBitsUse
| ChannelUpdate
| ChannelFollow
| ChannelAdBreakBegin
| ChannelChatClear
| ChannelChatClearUserMessages
| ChannelChatMessage
| ChannelChatMessageDelete
| ChannelChatNotification
| ChannelChatSettingsUpdate
| ChannelChatUserMessageHold
| ChannelChatUserMessageUpdate
| ChannelSharedChatSessionBegin
| ChannelSharedChatSessionUpdate
| ChannelSharedChatSessionEnd
| ChannelSubscribe
| ChannelSubscriptionEnd
| ChannelSubscriptionGift
| ChannelSubscriptionMessage
| ChannelCheer
| ChannelRaid
| ChannelBan
| ChannelUnban
| ChannelUnbanRequestCreate
| ChannelUnbanRequestResolve
| ChannelModerate
| ChannelModeratorAdd
| ChannelModeratorRemove
| ChannelPointsAutomaticRewardRedemptionAdd
| ChannelPointsCustomRewardAdd
| ChannelPointsCustomRewardUpdate
| ChannelPointsCustomRewardRemove
| ChannelPointsCustomRewardRedemptionAdd
| ChannelPointsCustomRewardRedemptionUpdate
| ChannelPollBegin
| ChannelPollProgress
| ChannelPollEnd
| ChannelPredictionBegin
| ChannelPredictionProgress
| ChannelPredictionLock
| ChannelPredictionEnd
| ChannelSuspiciousUserMessage
| ChannelSuspiciousUserUpdate
| ChannelVIPAdd
| ChannelVIPRemove
| ChannelWarningAcknowledgement
| ChannelWarningSend
| CharityDonation
| CharityCampaignStart
| CharityCampaignProgress
| CharityCampaignStop
| ConduitShardDisabled
| DropEntitlementGrant
| ExtensionBitsTransactionCreate
| GoalBegin
| GoalProgress
| GoalEnd
| HypeTrainBegin
| HypeTrainProgress
| HypeTrainEnd
| ShoutoutCreate
| ShoutoutReceived
| StreamOnline
| StreamOffline
| UserAuthorizationGrant
| UserAuthorizationRevoke
| UserUpdate
| WhisperReceived
)

View File

@ -0,0 +1,22 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel
class BaseWebhookTransport(BaseModel):
method: Literal['webhook']
callback: str
class BaseWebsocketTransport(BaseModel):
method: Literal['websocket']
session_id: str
connected_at: datetime
class SubscribeWebhookTransport(BaseWebhookTransport):
secret: str
Any = BaseWebhookTransport | BaseWebsocketTransport

View File

@ -0,0 +1,201 @@
from typing import Literal
# Automod types
AUTOMOD_MESSAGE_HOLD = Literal['automod.message.hold']
AUTOMOD_MESSAGE_UPDATE = Literal['automod.message.update']
AUTOMOD_SETTINGS_UPDATE = Literal['automod.settings.update']
AUTOMOD_TERMS_UPDATE = Literal['automod.terms.update']
# Channel types
CHANNEL_BITS_USE = Literal['channel.bits.use']
CHANNEL_UPDATE = Literal['channel.update']
CHANNEL_FOLLOW = Literal['channel.follow']
CHANNEL_AD_BREAK_BEGIN = Literal['channel.ad_break.begin']
CHANNEL_CHAT_CLEAR = Literal['channel.chat.clear']
CHANNEL_CHAT_CLEAR_USER_MESSAGES = Literal['channel.chat.clear_user_messages']
CHANNEL_CHAT_MESSAGE = Literal['channel.chat.message']
CHANNEL_CHAT_MESSAGE_DELETE = Literal['channel.chat.message_delete']
CHANNEL_CHAT_NOTIFICATION = Literal['channel.chat.notification']
CHANNEL_CHAT_SETTINGS_UPDATE = Literal['channel.chat_settings.update']
CHANNEL_CHAT_USER_MESSAGE_HOLD = Literal['channel.chat.user_message_hold']
CHANNEL_CHAT_USER_MESSAGE_UPDATE = Literal['channel.chat.user_message_update']
CHANNEL_SHARED_CHAT_SESSION_BEGIN = Literal['channel.shared_chat.begin']
CHANNEL_SHARED_CHAT_SESSION_UPDATE = Literal['channel.shared_chat.update']
CHANNEL_SHARED_CHAT_SESSION_END = Literal['channel.shared_chat.end']
CHANNEL_SUBSCRIBE = Literal['channel.subscribe']
CHANNEL_SUBSCRIPTION_END = Literal['channel.subscription.end']
CHANNEL_SUBSCRIPTION_GIFT = Literal['channel.subscription.gift']
CHANNEL_SUBSCRIPTION_MESSAGE = Literal['channel.subscription.message']
CHANNEL_CHEER = Literal['channel.cheer']
CHANNEL_RAID = Literal['channel.raid']
CHANNEL_BAN = Literal['channel.ban']
CHANNEL_UNBAN = Literal['channel.unban']
CHANNEL_UNBAN_REQUEST_CREATE = Literal['channel.unban_request.create']
CHANNEL_UNBAN_REQUEST_RESOLVE = Literal['channel.unban_request.resolve']
CHANNEL_MODERATE = Literal['channel.moderate']
CHANNEL_MODERATOR_ADD = Literal['channel.moderator.add']
CHANNEL_MODERATOR_REMOVE = Literal['channel.moderator.remove']
CHANNEL_GUEST_STAR_SESSION_BEGIN = Literal['channel.guest_star_session.begin']
CHANNEL_GUEST_STAR_SESSION_END = Literal['channel.guest_star_session.end']
CHANNEL_GUEST_STAR_GUEST_UPDATE = Literal['channel.guest_star_guest.update']
CHANNEL_GUEST_STAR_SETTINGS_UPDATE = Literal[
'channel.guest_star_settings.update'
]
CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD = Literal[
'channel.channel_points_automatic_reward_redemption.add'
]
CHANNEL_POINTS_CUSTOM_REWARD_ADD = Literal[
'channel.channel_points_custom_reward.add'
]
CHANNEL_POINTS_CUSTOM_REWARD_UPDATE = Literal[
'channel.channel_points_custom_reward.update'
]
CHANNEL_POINTS_CUSTOM_REWARD_REMOVE = Literal[
'channel.channel_points_custom_reward.remove'
]
CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD = Literal[
'channel.channel_points_custom_reward_redemption.add'
]
CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE = Literal[
'channel.channel_points_custom_reward_redemption.update'
]
CHANNEL_POLL_BEGIN = Literal['channel.poll.begin']
CHANNEL_POLL_PROGRESS = Literal['channel.poll.progress']
CHANNEL_POLL_END = Literal['channel.poll.end']
CHANNEL_PREDICTION_BEGIN = Literal['channel.prediction.begin']
CHANNEL_PREDICTION_PROGRESS = Literal['channel.prediction.progress']
CHANNEL_PREDICTION_LOCK = Literal['channel.prediction.lock']
CHANNEL_PREDICTION_END = Literal['channel.prediction.end']
CHANNEL_SUSPICIOUS_USER_MESSAGE = Literal['channel.suspicious_user.message']
CHANNEL_SUSPICIOUS_USER_UPDATE = Literal['channel.suspicious_user.update']
CHANNEL_VIP_ADD = Literal['channel.vip.add']
CHANNEL_VIP_REMOVE = Literal['channel.vip.remove']
CHANNEL_WARNING_ACKNOWLEDGEMENT = Literal['channel.warning.acknowledge']
CHANNEL_WARNING_SEND = Literal['channel.warning.send']
# Charity types
CHARITY_DONATION = Literal['channel.charity_campaign.donate']
CHARITY_CAMPAIGN_START = Literal['channel.charity_campaign.start']
CHARITY_CAMPAIGN_PROGRESS = Literal['channel.charity_campaign.progress']
CHARITY_CAMPAIGN_STOP = Literal['channel.charity_campaign.stop']
# Conduit types
CONDUIT_SHARD_DISABLED = Literal['conduit.shard.disabled']
# Drop types
DROP_ENTITLEMENT_GRANT = Literal['drop.entitlement.grant']
# Extension types
EXTENSION_BITS_TRANSACTION_CREATE = Literal[
'extension.bits_transaction.create'
]
# Goal types
GOAL_BEGIN = Literal['channel.goal.begin']
GOAL_PROGRESS = Literal['channel.goal.progress']
GOAL_END = Literal['channel.goal.end']
# Hype Train types
HYPE_TRAIN_BEGIN = Literal['channel.hype_train.begin']
HYPE_TRAIN_PROGRESS = Literal['channel.hype_train.progress']
HYPE_TRAIN_END = Literal['channel.hype_train.end']
# Shield Mode types
SHIELD_MODE_BEGIN = Literal['channel.shield_mode.begin']
SHIELD_MODE_END = Literal['channel.shield_mode.end']
# Shoutout types
SHOUTOUT_CREATE = Literal['channel.shoutout.create']
SHOUTOUT_RECEIVED = Literal['channel.shoutout.receive']
# Stream types
STREAM_ONLINE = Literal['stream.online']
STREAM_OFFLINE = Literal['stream.offline']
# User types
USER_AUTHORIZATION_GRANT = Literal['user.authorization.grant']
USER_AUTHORIZATION_REVOKE = Literal['user.authorization.revoke']
USER_UPDATE = Literal['user.update']
WHISPER_RECEIVED = Literal['user.whisper.message']
# Union type for all events
Any = (
AUTOMOD_MESSAGE_HOLD
| AUTOMOD_MESSAGE_UPDATE
| AUTOMOD_SETTINGS_UPDATE
| AUTOMOD_TERMS_UPDATE
| CHANNEL_BITS_USE
| CHANNEL_UPDATE
| CHANNEL_FOLLOW
| CHANNEL_AD_BREAK_BEGIN
| CHANNEL_CHAT_CLEAR
| CHANNEL_CHAT_CLEAR_USER_MESSAGES
| CHANNEL_CHAT_MESSAGE
| CHANNEL_CHAT_MESSAGE_DELETE
| CHANNEL_CHAT_NOTIFICATION
| CHANNEL_CHAT_SETTINGS_UPDATE
| CHANNEL_CHAT_USER_MESSAGE_HOLD
| CHANNEL_CHAT_USER_MESSAGE_UPDATE
| CHANNEL_SHARED_CHAT_SESSION_BEGIN
| CHANNEL_SHARED_CHAT_SESSION_UPDATE
| CHANNEL_SHARED_CHAT_SESSION_END
| CHANNEL_SUBSCRIBE
| CHANNEL_SUBSCRIPTION_END
| CHANNEL_SUBSCRIPTION_GIFT
| CHANNEL_SUBSCRIPTION_MESSAGE
| CHANNEL_CHEER
| CHANNEL_RAID
| CHANNEL_BAN
| CHANNEL_UNBAN
| CHANNEL_UNBAN_REQUEST_CREATE
| CHANNEL_UNBAN_REQUEST_RESOLVE
| CHANNEL_MODERATE
| CHANNEL_MODERATOR_ADD
| CHANNEL_MODERATOR_REMOVE
| CHANNEL_GUEST_STAR_SESSION_BEGIN
| CHANNEL_GUEST_STAR_SESSION_END
| CHANNEL_GUEST_STAR_GUEST_UPDATE
| CHANNEL_GUEST_STAR_SETTINGS_UPDATE
| CHANNEL_POINTS_AUTOMATIC_REWARD_REDEMPTION_ADD
| CHANNEL_POINTS_CUSTOM_REWARD_ADD
| CHANNEL_POINTS_CUSTOM_REWARD_UPDATE
| CHANNEL_POINTS_CUSTOM_REWARD_REMOVE
| CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_ADD
| CHANNEL_POINTS_CUSTOM_REWARD_REDEMPTION_UPDATE
| CHANNEL_POLL_BEGIN
| CHANNEL_POLL_PROGRESS
| CHANNEL_POLL_END
| CHANNEL_PREDICTION_BEGIN
| CHANNEL_PREDICTION_PROGRESS
| CHANNEL_PREDICTION_LOCK
| CHANNEL_PREDICTION_END
| CHANNEL_SUSPICIOUS_USER_MESSAGE
| CHANNEL_SUSPICIOUS_USER_UPDATE
| CHANNEL_VIP_ADD
| CHANNEL_VIP_REMOVE
| CHANNEL_WARNING_ACKNOWLEDGEMENT
| CHANNEL_WARNING_SEND
| CHARITY_DONATION
| CHARITY_CAMPAIGN_START
| CHARITY_CAMPAIGN_PROGRESS
| CHARITY_CAMPAIGN_STOP
| CONDUIT_SHARD_DISABLED
| DROP_ENTITLEMENT_GRANT
| EXTENSION_BITS_TRANSACTION_CREATE
| GOAL_BEGIN
| GOAL_PROGRESS
| GOAL_END
| HYPE_TRAIN_BEGIN
| HYPE_TRAIN_PROGRESS
| HYPE_TRAIN_END
| SHIELD_MODE_BEGIN
| SHIELD_MODE_END
| SHOUTOUT_CREATE
| SHOUTOUT_RECEIVED
| STREAM_ONLINE
| STREAM_OFFLINE
| USER_AUTHORIZATION_GRANT
| USER_AUTHORIZATION_REVOKE
| USER_UPDATE
| WHISPER_RECEIVED
)

File diff suppressed because it is too large Load Diff

166
src/twitchclient/scopes.py Normal file
View File

@ -0,0 +1,166 @@
# Analytics
from typing import Literal
ANALYTICS_READ_EXTENSIONS = 'analytics:read:extensions'
ANALYTICS_READ_GAMES = 'analytics:read:games'
# Bits
BITS_READ = 'bits:read'
# Channel
CHANNEL_MANAGE_ADS = 'channel:manage:ads'
CHANNEL_READ_ADS = 'channel:read:ads'
CHANNEL_MANAGE_BROADCAST = 'channel:manage:broadcast'
CHANNEL_READ_CHARITY = 'channel:read:charity'
CHANNEL_EDIT_COMMERCIAL = 'channel:edit:commercial'
CHANNEL_READ_EDITORS = 'channel:read:editors'
CHANNEL_MANAGE_EXTENSIONS = 'channel:manage:extensions'
CHANNEL_READ_GOALS = 'channel:read:goals'
CHANNEL_READ_GUEST_STAR = 'channel:read:guest_star'
CHANNEL_MANAGE_GUEST_STAR = 'channel:manage:guest_star'
CHANNEL_READ_HYPE_TRAIN = 'channel:read:hype_train'
CHANNEL_MANAGE_MODERATORS = 'channel:manage:moderators'
CHANNEL_READ_POLLS = 'channel:read:polls'
CHANNEL_MANAGE_POLLS = 'channel:manage:polls'
CHANNEL_READ_PREDICTIONS = 'channel:read:predictions'
CHANNEL_MANAGE_PREDICTIONS = 'channel:manage:predictions'
CHANNEL_MANAGE_RAIDS = 'channel:manage:raids'
CHANNEL_READ_REDEMPTIONS = 'channel:read:redemptions'
CHANNEL_MANAGE_REDEMPTIONS = 'channel:manage:redemptions'
CHANNEL_MANAGE_SCHEDULE = 'channel:manage:schedule'
CHANNEL_READ_STREAM_KEY = 'channel:read:stream_key'
CHANNEL_READ_SUBSCRIPTIONS = 'channel:read:subscriptions'
CHANNEL_MANAGE_VIDEOS = 'channel:manage:videos'
CHANNEL_READ_VIPS = 'channel:read:vips'
CHANNEL_MANAGE_VIPS = 'channel:manage:vips'
CHANNEL_BOT = 'channel:bot'
CHANNEL_MODERATE = 'channel:moderate'
# clips
CLIPS_EDIT = 'clips:edit'
# Moderation
MODERATION_READ = 'moderation:read'
MODERATOR_MANAGE_ANNOUNCEMENTS = 'moderator:manage:announcements'
MODERATOR_MANAGE_AUTOMOD = 'moderator:manage:automod'
MODERATOR_READ_AUTOMOD_SETTINGS = 'moderator:read:automod_settings'
MODERATOR_MANAGE_AUTOMOD_SETTINGS = 'moderator:manage:automod_settings'
MODERATOR_MANAGE_BANNED_USERS = 'moderator:manage:banned_users'
MODERATOR_READ_BLOCKED_TERMS = 'moderator:read:blocked_terms'
MODERATOR_MANAGE_BLOCKED_TERMS = 'moderator:manage:blocked_terms'
MODERATOR_MANAGE_CHAT_MESSAGES = 'moderator:manage:chat_messages'
MODERATOR_READ_CHAT_SETTINGS = 'moderator:read:chat_settings'
MODERATOR_MANAGE_CHAT_SETTINGS = 'moderator:manage:chat_settings'
MODERATOR_READ_CHATTERS = 'moderator:read:chatters'
MODERATOR_READ_FOLLOWERS = 'moderator:read:followers'
MODERATOR_READ_GUEST_STAR = 'moderator:read:guest_star'
MODERATOR_MANAGE_GUEST_STAR = 'moderator:manage:guest_star'
MODERATOR_READ_SHIELD_MODE = 'moderator:read:shield_mode'
MODERATOR_MANAGE_SHIELD_MODE = 'moderator:manage:shield_mode'
MODERATOR_READ_SHOUTOUTS = 'moderator:read:shoutouts'
MODERATOR_MANAGE_SHOUTOUTS = 'moderator:manage:shoutouts'
MODERATOR_READ_UNBAN_REQUESTS = 'moderator:read:unban_requests'
MODERATOR_MANAGE_UNBAN_REQUESTS = 'moderator:manage:unban_requests'
MODERATOR_READ_WARNINGS = 'moderator:read:warnings'
MODERATOR_MANAGE_WARNINGS = 'moderator:manage:warnings'
# User
USER_EDIT = 'user:edit'
USER_EDIT_FOLLOWS = 'user:edit:follows'
USER_READ_BLOCKED_USERS = 'user:read:blocked_users'
USER_MANAGE_BLOCKED_USERS = 'user:manage:blocked_users'
USER_READ_BROADCAST = 'user:read:broadcast'
USER_MANAGE_CHAT_COLOR = 'user:manage:chat_color'
USER_READ_EMAIL = 'user:read:email'
USER_READ_EMOTES = 'user:read:emotes'
USER_READ_FOLLOWS = 'user:read:follows'
USER_READ_MODERATED_CHANNELS = 'user:read:moderated_channels'
USER_READ_SUBSCRIPTIONS = 'user:read:subscriptions'
USER_MANAGE_WHISPERS = 'user:manage:whispers'
USER_BOT = 'user:bot'
USER_READ_CHAT = 'user:read:chat'
USER_WRITE_CHAT = 'user:write:chat'
# Chat
CHAT_EDIT = 'chat:edit'
CHAT_READ = 'chat:read'
# Whispers
WHISPERS_READ = 'whispers:read'
WHISPERS_EDIT = 'whispers:edit'
type Any = Literal[
'analytics:read:extensions',
'analytics:read:games',
'bits:read',
'channel:manage:ads',
'channel:read:ads',
'channel:manage:broadcast',
'channel:read:charity',
'channel:edit:commercial',
'channel:read:editors',
'channel:manage:extensions',
'channel:read:goals',
'channel:read:guest_star',
'channel:manage:guest_star',
'channel:read:hype_train',
'channel:manage:moderators',
'channel:read:polls',
'channel:manage:polls',
'channel:read:predictions',
'channel:manage:predictions',
'channel:manage:raids',
'channel:read:redemptions',
'channel:manage:redemptions',
'channel:manage:schedule',
'channel:read:stream_key',
'channel:read:subscriptions',
'channel:manage:videos',
'channel:read:vips',
'channel:manage:vips',
'channel:bot',
'channel:moderate',
'clips:edit',
'moderation:read',
'moderator:manage:announcements',
'moderator:manage:automod',
'moderator:read:automod_settings',
'moderator:manage:automod_settings',
'moderator:manage:banned_users',
'moderator:read:blocked_terms',
'moderator:manage:blocked_terms',
'moderator:manage:chat_messages',
'moderator:read:chat_settings',
'moderator:manage:chat_settings',
'moderator:read:chatters',
'moderator:read:followers',
'moderator:read:guest_star',
'moderator:manage:guest_star',
'moderator:read:shield_mode',
'moderator:manage:shield_mode',
'moderator:read:shoutouts',
'moderator:manage:shoutouts',
'moderator:read:unban_requests',
'moderator:manage:unban_requests',
'moderator:read:warnings',
'moderator:manage:warnings',
'user:edit',
'user:edit:follows',
'user:read:blocked_users',
'user:manage:blocked_users',
'user:read:broadcast',
'user:manage:chat_color',
'user:read:email',
'user:read:emotes',
'user:read:follows',
'user:read:moderated_channels',
'user:read:subscriptions',
'user:manage:whispers',
'user:bot',
'user:read:chat',
'user:write:chat',
'chat:edit',
'chat:read',
'whispers:read',
'whispers:edit',
]