Skip to content

Commit 079442e

Browse files
committed
Add authorisation_by_user endpoint support
commit 120a81c Author: chillymosh <86857777+chillymosh@users.noreply.github.com> Date: Sun Nov 2 12:51:30 2025 +0000 ruff format commit 25f444f Author: chillymosh <86857777+chillymosh@users.noreply.github.com> Date: Sun Nov 2 12:49:57 2025 +0000 Adjust Scope creation for old Kraken scopes commit 4fb2d61 Merge: 4503c2d c4b8257 Author: chillymosh <86857777+chillymosh@users.noreply.github.com> Date: Sun Nov 2 10:37:52 2025 +0000 Merge branch 'main' into endpoint/get_auth_user commit 4503c2d Author: chillymosh <86857777+chillymosh@users.noreply.github.com> Date: Sun Oct 19 16:16:41 2025 +0100 Add authorisation_by_user endpoint support
1 parent 8ed86fd commit 079442e

File tree

7 files changed

+109
-6
lines changed

7 files changed

+109
-6
lines changed

docs/getting-started/changelog.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ Changelog
1010
======
1111
- twitchio
1212
- Additions
13-
- Added - :func:`~twitchio.PartialUser.fetch_hype_train_status`
14-
- Added - :func:`~twitchio.PartialUser.fetch_stream`
13+
- Added - :class:`~twitchio.UserAuthorisation` model
14+
- Added - :func:`~twitchio.Client.fetch_auth_by_users` to :class:`~twitchio.Client`
15+
- Added - :func:`~twitchio.PartialUser.fetch_auth` to :class:`~twitchio.PartialUser`
16+
- Added - :func:`~twitchio.PartialUser.fetch_stream` to :class:`~twitchio.PartialUser` as a helper method.
17+
- Added - :func:`~twitchio.PartialUser.fetch_hype_train_status` to :class:`~twitchio.PartialUser`.
18+
This replaces :func:`~twitchio.PartialUser.fetch_hype_train_events` which has been deprecated
1519

1620
- Bug fixes
1721
- Fix :func:`~twitchio.utils.setup_logging` breaking coloured formatting on `CRITICAL` logging level

docs/references/helix/helix_models.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,11 @@
367367
.. autoclass:: twitchio.VideoMarkers
368368
:members:
369369

370+
.. attributetable:: twitchio.UserAuthorisation
371+
372+
.. autoclass:: twitchio.UserAuthorisation
373+
:members:
374+
370375
.. attributetable:: twitchio.UserSubscription
371376

372377
.. autoclass:: twitchio.UserSubscription

twitchio/authentication/scopes.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,9 +355,16 @@ def __init__(self, scopes: Iterable[str | _scope_property] | None = None, /, **k
355355
if scope == "openid":
356356
continue
357357

358-
prop = getattr(self, scope.replace(":", "_"))
359-
elif isinstance(scope, _scope_property): # type: ignore
358+
attr_name = scope.replace(":", "_")
359+
360+
if not hasattr(self, attr_name):
361+
continue
362+
363+
prop = getattr(self, attr_name)
364+
365+
elif isinstance(scope, _scope_property): # type: ignore[unnecessary-isinstance]
360366
prop = scope
367+
361368
else:
362369
raise TypeError(f"Invalid scope provided: {type(scope)} is not a valid scope.")
363370

twitchio/client.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from .models.games import Game
4545
from .models.teams import Team
4646
from .payloads import EventErrorPayload, WebsocketSubscriptionData
47-
from .user import ActiveExtensions, Extension, PartialUser, User
47+
from .user import ActiveExtensions, Extension, PartialUser, User, UserAuthorisation
4848
from .utils import MISSING, EventWaiter, clamp, unwrap_function
4949
from .web import AiohttpAdapter, has_starlette
5050
from .web.utils import BaseAdapter
@@ -2211,6 +2211,32 @@ async def update_entitlements(
22112211
data = await self._http.patch_drop_entitlements(ids=ids, fulfillment_status=fulfillment_status, token_for=token_for)
22122212
return [EntitlementStatus(d) for d in data["data"]]
22132213

2214+
async def fetch_auth_by_users(self, user_ids: list[str]) -> list[UserAuthorisation]:
2215+
"""|coro|
2216+
Fetches authentication information for one or more users, up to 10.
2217+
This uses the app token associated with the Client ID and Secret provided.
2218+
2219+
Parameters
2220+
----------
2221+
user_ids: list[str]
2222+
A list of user IDs to fetch authentication information for.
2223+
You may only fetch a maximum of `10` user IDs at a time.
2224+
2225+
Returns
2226+
-------
2227+
list[UserAuthorisation]
2228+
2229+
Raises
2230+
------
2231+
ValueError
2232+
You may only fetch a maximum of ``10`` user IDs at a time.
2233+
"""
2234+
if len(user_ids) > 10:
2235+
raise ValueError("You may only fetch a maximum of 10 user IDs at a time.")
2236+
2237+
data = await self._http.get_auth_by_user(user_ids=user_ids)
2238+
return [UserAuthorisation(u, http=self._http) for u in data["data"]]
2239+
22142240
async def subscribe_websocket(
22152241
self,
22162242
payload: SubscriptionPayload,

twitchio/http.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
from .types_.responses import (
8282
AddBlockedTermResponse,
8383
AdScheduleResponse,
84+
AuthorizationByUserResponse,
8485
AutomodSettingsResponse,
8586
BannedUsersResponseData,
8687
BanUserResponse,
@@ -2510,6 +2511,11 @@ async def put_user(self, token_for: str, description: str | None) -> UpdateUserR
25102511
route: Route = Route("PUT", "users", params=params, token_for=token_for)
25112512
return await self.request_json(route)
25122513

2514+
async def get_auth_by_user(self, user_ids: list[str]) -> AuthorizationByUserResponse:
2515+
params = {"user_id": user_ids}
2516+
route: Route = Route("GET", "authorization/users", params=params)
2517+
return await self.request_json(route)
2518+
25132519
def get_user_block_list(
25142520
self, broadcaster_id: str | int, token_for: str, first: int = 20, max_results: int | None = None
25152521
) -> HTTPAsyncIterator[PartialUser]:

twitchio/types_/responses.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
__all__ = (
3131
"AdScheduleResponse",
3232
"AdScheduleResponseData",
33+
"AuthorizationByUserResponse",
34+
"AuthorizationByUserResponseData",
3335
"AuthorizationURLResponse",
3436
"BitsLeaderboardResponse",
3537
"ChannelChatBadgesResponseData",
@@ -1663,6 +1665,18 @@ class UpdateUserResponse(TypedDict):
16631665
data: list[UsersResponseData]
16641666

16651667

1668+
class AuthorizationByUserResponseData(TypedDict):
1669+
user_id: str
1670+
user_name: str
1671+
user_login: str
1672+
scopes: list[str]
1673+
has_authorized: bool
1674+
1675+
1676+
class AuthorizationByUserResponse(TypedDict):
1677+
data: list[AuthorizationByUserResponseData]
1678+
1679+
16661680
class UserBlockListResponseData(TypedDict):
16671681
user_id: str
16681682
user_login: str

twitchio/user.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import datetime
3838

3939
from twitchio.types_.responses import (
40+
AuthorizationByUserResponseData,
4041
UserActiveExtensionsResponseData,
4142
UserExtensionsResponseData,
4243
UserPanelComponentItem,
@@ -75,7 +76,7 @@
7576
from .models.subscriptions import BroadcasterSubscriptions, UserSubscription
7677
from .models.teams import ChannelTeam
7778

78-
__all__ = ("ActiveExtensions", "Extension", "PartialUser", "User")
79+
__all__ = ("ActiveExtensions", "Extension", "PartialUser", "User", "UserAuthorisation")
7980

8081

8182
class PartialUser:
@@ -3211,6 +3212,20 @@ async def update(self, description: str | None = None) -> User:
32113212
data = await self._http.put_user(token_for=self.id, description=description)
32123213
return User(data["data"][0], http=self._http)
32133214

3215+
async def fetch_auth(self) -> UserAuthorisation:
3216+
"""|coro|
3217+
3218+
Fetches the user's authentication information.
3219+
3220+
Returns
3221+
-------
3222+
UserAuthorisation
3223+
UserAuthorisation object.
3224+
"""
3225+
3226+
data = await self._http.get_auth_by_user(user_ids=[self.id])
3227+
return UserAuthorisation(data["data"][0], http=self._http)
3228+
32143229
def fetch_block_list(self, *, first: int = 20, max_results: int | None = None) -> HTTPAsyncIterator[PartialUser]:
32153230
"""|aiter|
32163231
@@ -4087,3 +4102,29 @@ async def follow_info(self) -> ChannelFollowerEvent | None:
40874102
user_id=self, broadcaster_id=self.channel, token_for=self.channel, max_results=1
40884103
)
40894104
return await anext(data.followers, None)
4105+
4106+
4107+
class UserAuthorisation:
4108+
"""A class that contains authorisation information for a user against a Client ID / Twitch application.
4109+
4110+
Attributes
4111+
-----------
4112+
user: PartialUser
4113+
The user having authorised checked.
4114+
scopes: Scopes
4115+
The scopes the user has granted to the Client ID / application.
4116+
authorised: bool
4117+
Whether the user has authorised the Client ID / application.
4118+
"""
4119+
4120+
__slots__ = ("authorised", "scopes", "user")
4121+
4122+
def __init__(self, data: AuthorizationByUserResponseData, *, http: HTTPClient) -> None:
4123+
from .authentication import Scopes
4124+
4125+
self.user: PartialUser = PartialUser(data["user_id"], data["user_login"], data["user_name"], http=http)
4126+
self.scopes: Scopes = Scopes(data["scopes"])
4127+
self.authorised: bool = bool(data["has_authorized"])
4128+
4129+
def __repr__(self) -> str:
4130+
return f"<UserAuthorisation user={self.user} authorised={self.authorised}>"

0 commit comments

Comments
 (0)