Skip to content

Commit c6fbee0

Browse files
committed
New PartialUser following methods
Add deprecation warnings for follower endpoints Add new methods and models
1 parent a40e56f commit c6fbee0

File tree

5 files changed

+266
-3
lines changed

5 files changed

+266
-3
lines changed

docs/changelog.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
33
Master
44
======
55
- TwitchIO
6+
- Additions
7+
- Added the new follower / followed endpoints for :class:`~twitchio.PartialUser`:
8+
- :func:`~twitchio.PartialUser.fetch_channel_followers`
9+
- :func:`~twitchio.PartialUser.fetch_channel_following`
10+
- :func:`~twitchio.PartialUser.fetch_channel_follower_count`
11+
- :func:`~twitchio.PartialUser.fetch_channel_following_count`
12+
- The deprecated methods have had warnings added in the docs.
13+
- New models for the new methods have been added:
14+
- :class:`~twitchio.ChannelFollowerEvent`
15+
- :class:`~twitchio.ChannelFollowingEvent`
16+
617
- Bug fixes
718
- Fix IndexError when getting prefix when empty message is sent in a reply.
819

docs/reference.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,22 @@ ChannelEmote
7474
:members:
7575
:inherited-members:
7676

77+
ChannelFollowerEvent
78+
---------------------
79+
.. attributetable:: ChannelFollowerEvent
80+
81+
.. autoclass:: ChannelFollowerEvent
82+
:members:
83+
:inherited-members:
84+
85+
ChannelFollowingEvent
86+
---------------------
87+
.. attributetable:: ChannelFollowingEvent
88+
89+
.. autoclass:: ChannelFollowingEvent
90+
:members:
91+
:inherited-members:
92+
7793
ChannelInfo
7894
------------
7995
.. attributetable:: ChannelInfo

twitchio/http.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,3 +1178,67 @@ async def get_channel_charity_campaigns(self, broadcaster_id: str, token: str):
11781178
return await self.request(
11791179
Route("GET", "charity/campaigns", query=[("broadcaster_id", broadcaster_id)], token=token)
11801180
)
1181+
1182+
async def get_channel_followers(
1183+
self,
1184+
token: str,
1185+
broadcaster_id: str,
1186+
user_id: Optional[int] = None,
1187+
):
1188+
query = [("broadcaster_id", broadcaster_id)]
1189+
1190+
if user_id is not None:
1191+
query.append(("user_id", str(user_id)))
1192+
1193+
return await self.request(
1194+
Route(
1195+
"GET",
1196+
"channels/followers",
1197+
query=query,
1198+
token=token,
1199+
)
1200+
)
1201+
1202+
async def get_channel_followed(
1203+
self,
1204+
token: str,
1205+
user_id: str,
1206+
broadcaster_id: Optional[int] = None,
1207+
):
1208+
query = [("user_id", user_id)]
1209+
1210+
if broadcaster_id is not None:
1211+
query.append(("broadcaster_id", str(broadcaster_id)))
1212+
1213+
return await self.request(
1214+
Route(
1215+
"GET",
1216+
"channels/followed",
1217+
query=query,
1218+
token=token,
1219+
)
1220+
)
1221+
1222+
async def get_channel_follower_count(self, broadcaster_id: str, token: Optional[str] = None):
1223+
return await self.request(
1224+
Route(
1225+
"GET",
1226+
"channels/followers",
1227+
query=[("broadcaster_id", broadcaster_id)],
1228+
token=token,
1229+
),
1230+
full_body=True,
1231+
paginate=False,
1232+
)
1233+
1234+
async def get_channel_followed_count(self, token: str, user_id: str):
1235+
return await self.request(
1236+
Route(
1237+
"GET",
1238+
"channels/followed",
1239+
query=[("user_id", user_id)],
1240+
token=token,
1241+
),
1242+
full_body=True,
1243+
paginate=False,
1244+
)

twitchio/models.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@
8282
"ContentClassificationLabel",
8383
"CharityValues",
8484
"CharityCampaign",
85+
"ChannelFollowerEvent",
86+
"ChannelFollowingEvent",
8587
)
8688

8789

@@ -510,6 +512,60 @@ def __repr__(self):
510512
return f"<FollowEvent from_user={self.from_user} to_user={self.to_user} followed_at={self.followed_at}>"
511513

512514

515+
class ChannelFollowerEvent:
516+
"""
517+
Represents a ChannelFollowEvent Event.
518+
519+
Attributes
520+
-----------
521+
user: Union[:class:`~twitchio.User`, :class:`~twitchio.PartialUser`]
522+
The user that followed another user.
523+
followed_at: :class:`datetime.datetime`
524+
When the follow happened.
525+
"""
526+
527+
__slots__ = "user", "followed_at"
528+
529+
def __init__(
530+
self,
531+
http: "TwitchHTTP",
532+
data: dict,
533+
):
534+
self.user: Union[User, PartialUser] = PartialUser(http, data["user_id"], data["user_login"])
535+
self.followed_at = parse_timestamp(data["followed_at"])
536+
537+
def __repr__(self):
538+
return f"<ChannelFollowerEvent user={self.user} followed_at={self.followed_at}>"
539+
540+
541+
class ChannelFollowingEvent:
542+
"""
543+
Represents a ChannelFollowEvent Event.
544+
545+
Attributes
546+
-----------
547+
broadcaster: Union[:class:`~twitchio.User`, :class:`~twitchio.PartialUser`]
548+
The user that is following another user.
549+
followed_at: :class:`datetime.datetime`
550+
When the follow happened.
551+
"""
552+
553+
__slots__ = "broadcaster", "followed_at"
554+
555+
def __init__(
556+
self,
557+
http: "TwitchHTTP",
558+
data: dict,
559+
):
560+
self.broadcaster: Union[User, PartialUser] = PartialUser(
561+
http, data["broadcaster_id"], data["broadcaster_login"]
562+
)
563+
self.followed_at = parse_timestamp(data["followed_at"])
564+
565+
def __repr__(self):
566+
return f"<ChannelFollowerEvent user={self.broadcaster} followed_at={self.followed_at}>"
567+
568+
513569
class SubscriptionEvent:
514570
"""
515571
Represents a Subscription Event

twitchio/user.py

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,25 @@
2828
from typing import TYPE_CHECKING, List, Optional, Union, Tuple, Dict
2929

3030
from .enums import BroadcasterTypeEnum, UserTypeEnum
31-
from .errors import HTTPException, Unauthorized
31+
from .errors import HTTPException
3232
from .rewards import CustomReward
3333
from .utils import parse_timestamp
3434

3535

3636
if TYPE_CHECKING:
3737
from .http import TwitchHTTP
3838
from .channel import Channel
39-
from .models import BitsLeaderboard, Clip, ExtensionBuilder, Tag, FollowEvent, Prediction, CharityCampaign
39+
from .models import (
40+
BitsLeaderboard,
41+
Clip,
42+
ExtensionBuilder,
43+
Tag,
44+
FollowEvent,
45+
Prediction,
46+
CharityCampaign,
47+
ChannelFollowerEvent,
48+
ChannelFollowingEvent,
49+
)
4050
__all__ = (
4151
"PartialUser",
4252
"BitLeaderboardUser",
@@ -491,6 +501,10 @@ async def fetch_following(self, token: Optional[str] = None) -> List["FollowEven
491501
492502
Fetches a list of users that this user is following.
493503
504+
.. warning::
505+
506+
The endpoint this method uses has been deprecated by Twitch and is no longer available.
507+
494508
Parameters
495509
-----------
496510
token: Optional[:class:`str`]
@@ -510,6 +524,10 @@ async def fetch_followers(self, token: Optional[str] = None):
510524
511525
Fetches a list of users that are following this user.
512526
527+
.. warning::
528+
529+
The endpoint this method uses has been deprecated by Twitch and is no longer available.
530+
513531
Parameters
514532
-----------
515533
token: Optional[:class:`str`]
@@ -524,11 +542,65 @@ async def fetch_followers(self, token: Optional[str] = None):
524542
data = await self._http.get_user_follows(token=token, to_id=str(self.id))
525543
return [FollowEvent(self._http, d, to=self) for d in data]
526544

545+
async def fetch_channel_followers(self, token: str, user_id: Optional[int] = None) -> List[ChannelFollowerEvent]:
546+
"""|coro|
547+
548+
Fetches a list of users that are following this broadcaster.
549+
Requires a user access token that includes the moderator:read:followers scope.
550+
The ID in the broadcaster_id query parameter must match the user ID in the access token or the user must be a moderator for the specified broadcaster.
551+
552+
Parameters
553+
-----------
554+
token: :class:`str`
555+
User access token that includes the ``moderator:read:followers`` scope for the channel.
556+
user_id: Optional[:class:`int`]
557+
Use this parameter to see whether the user follows this broadcaster.
558+
559+
560+
Returns
561+
--------
562+
List[:class:`twitchio.FollowEvent`]
563+
"""
564+
from .models import ChannelFollowerEvent
565+
566+
data = await self._http.get_channel_followers(token=token, broadcaster_id=str(self.id), user_id=user_id)
567+
return [ChannelFollowerEvent(self._http, d) for d in data]
568+
569+
async def fetch_channel_following(
570+
self, token: str, broadcaster_id: Optional[int] = None
571+
) -> List[ChannelFollowingEvent]:
572+
"""|coro|
573+
574+
Fetches a list of users that this user follows.
575+
Requires a user access token that includes the ``user:read:follows`` scope.
576+
The ID in the broadcaster_id query parameter must match the user ID in the access token or the user must be a moderator for the specified broadcaster.
577+
578+
Parameters
579+
-----------
580+
token: :class:`str`
581+
User access token that includes the moderator:read:followers scope for the channel.
582+
broadcaster_id: Optional[:class:`int`]
583+
Use this parameter to see whether the user follows this broadcaster.
584+
585+
586+
Returns
587+
--------
588+
List[:class:`twitchio.ChannelFollowingEvent`]
589+
"""
590+
from .models import ChannelFollowingEvent
591+
592+
data = await self._http.get_channel_followed(token=token, user_id=str(self.id), broadcaster_id=broadcaster_id)
593+
return [ChannelFollowingEvent(self._http, d) for d in data]
594+
527595
async def fetch_follow(self, to_user: "PartialUser", token: Optional[str] = None):
528596
"""|coro|
529597
530598
Check if a user follows another user or when they followed a user.
531599
600+
.. warning::
601+
602+
The endpoint this method uses has been deprecated by Twitch and is no longer available.
603+
532604
Parameters
533605
-----------
534606
to_user: :class:`PartialUser`
@@ -546,10 +618,50 @@ async def fetch_follow(self, to_user: "PartialUser", token: Optional[str] = None
546618
data = await self._http.get_user_follows(token=token, from_id=str(self.id), to_id=str(to_user.id))
547619
return FollowEvent(self._http, data[0]) if data else None
548620

621+
async def fetch_channel_follower_count(self, token: Optional[str] = None) -> int:
622+
"""|coro|
623+
624+
Fetches the total number of users that are following this user.
625+
626+
Parameters
627+
-----------
628+
token: Optional[:class:`str`]
629+
An oauth token to use instead of the bots token
630+
631+
Returns
632+
--------
633+
:class:`int`
634+
"""
635+
636+
data = await self._http.get_channel_follower_count(token=token, broadcaster_id=str(self.id))
637+
return data["total"]
638+
639+
async def fetch_channel_following_count(self, token: str) -> int:
640+
"""|coro|
641+
642+
Fetches the total number of users that the user is following.
643+
644+
Parameters
645+
-----------
646+
token: :class:`str`
647+
An oauth token to use instead of the bots token
648+
649+
Returns
650+
--------
651+
:class:`int`
652+
"""
653+
654+
data = await self._http.get_channel_followed_count(token=token, user_id=str(self.id))
655+
return data["total"]
656+
549657
async def fetch_follower_count(self, token: Optional[str] = None) -> int:
550658
"""|coro|
551659
552-
Fetches a list of users that are following this user.
660+
Fetches the total number of users that are following this user.
661+
662+
.. warning::
663+
664+
The endpoint this method uses has been deprecated by Twitch and is no longer available.
553665
554666
Parameters
555667
-----------
@@ -569,6 +681,10 @@ async def fetch_following_count(self, token: Optional[str] = None) -> int:
569681
570682
Fetches a list of users that this user is following.
571683
684+
.. warning::
685+
686+
The endpoint this method uses has been deprecated by Twitch and is no longer available.
687+
572688
Parameters
573689
-----------
574690
token: Optional[:class:`str`]

0 commit comments

Comments
 (0)