Skip to content

Commit 79cb53e

Browse files
committed
New User methods and fix
User methods added for the new goals and chat settings endpoints. fetch_channel_teams now correctly returns empty list rather than unhandled error if no team is found.
1 parent 23f631e commit 79cb53e

File tree

5 files changed

+285
-6
lines changed

5 files changed

+285
-6
lines changed

docs/changelog.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ Massive documentation updates
66

77
- TwitchIO
88
- Removed unexpected loop termination from `WSConnection._close()`
9-
- Fix bug where # prefixed channel names in initial_channels would not trigger :func:`twitchio.Client.event_ready`
9+
- Fix bug where # prefixed channel names and capitals in initial_channels would not trigger :func:`twitchio.Client.event_ready`
1010
- :func:`User.create_clip` has been fixed by converting bool to string in http request
1111
- Poll endpoints added :func:`User.fetch_polls` :func:`User.create_poll` and :func:`User.end_poll`
1212
- :func:`Client.fetch_cheermotes` color attribute corrected
13+
- :func:`User.fetch_channel_teams` returns empty list if no teams found rather than unhandled error
14+
- :func:`User.fetch_goals` method added
15+
- :func:`User.fetch_chat_settings` and :func:`User.update_chat_settings` methods added
1316

1417
- ext.commands
1518
- :func:`Bot.handle_commands` now also invokes on threads / replies

docs/reference.rst

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

77+
ChatSettings
78+
-------------
79+
.. attributetable:: ChatSettings
80+
81+
.. autoclass:: ChatSettings
82+
:members:
83+
:inherited-members:
84+
7785
Chatter
7886
--------
7987
.. attributetable:: PartialChatter
@@ -159,6 +167,14 @@ Game
159167
:members:
160168
:inherited-members:
161169

170+
Goal
171+
------
172+
.. attributetable:: Goal
173+
174+
.. autoclass:: Goal
175+
:members:
176+
:inherited-members:
177+
162178
HypeTrainContribution
163179
-----------------------
164180
.. attributetable:: HypeTrainContribution

twitchio/http.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ async def get_teams(self, team_name: str = None, team_id: str = None):
859859

860860
async def get_channel_teams(self, broadcaster_id: str):
861861
q = [("broadcaster_id", broadcaster_id)]
862-
return await self.request(Route("GET", "teams/channel", query=q))
862+
return await self.request(Route("GET", "teams/channel", query=q), paginate=False, full_body=True)
863863

864864
async def get_polls(
865865
self,
@@ -921,3 +921,48 @@ async def post_poll(
921921
async def patch_poll(self, broadcaster_id: str, token: str, id: str, status: str):
922922
body = {"broadcaster_id": broadcaster_id, "id": id, "status": status}
923923
return await self.request(Route("PATCH", "polls", body=body, token=token))
924+
925+
async def get_goals(self, broadcaster_id: str, token: str):
926+
return await self.request(Route("GET", "goals", query=[("broadcaster_id", broadcaster_id)], token=token))
927+
928+
async def get_chat_settings(self, broadcaster_id: str, moderator_id: Optional[str] = None, token: Optional[str] = None):
929+
q = [("broadcaster_id", broadcaster_id)]
930+
if moderator_id and token:
931+
q.append(("moderator_id", moderator_id))
932+
return await self.request(Route("GET", "chat/settings", query=q, token=token))
933+
934+
async def patch_chat_settings(
935+
self,
936+
broadcaster_id: str,
937+
moderator_id: str,
938+
token: str,
939+
emote_mode: Optional[bool] = None,
940+
follower_mode: Optional[bool] = None,
941+
follower_mode_duration: Optional[int] = None,
942+
slow_mode: Optional[bool] = None,
943+
slow_mode_wait_time: Optional[int] = None,
944+
subscriber_mode: Optional[bool] = None,
945+
unique_chat_mode: Optional[bool] = None,
946+
non_moderator_chat_delay: Optional[bool] = None,
947+
non_moderator_chat_delay_duration: Optional[int] = None
948+
):
949+
if follower_mode_duration and follower_mode_duration > 129600:
950+
raise ValueError("follower_mode_duration must be below 129600")
951+
if slow_mode_wait_time and (slow_mode_wait_time < 3 or slow_mode_wait_time > 120):
952+
raise ValueError("slow_mode_wait_time must be between 3 and 120")
953+
if non_moderator_chat_delay_duration and non_moderator_chat_delay_duration not in {2, 4, 6}:
954+
raise ValueError("non_moderator_chat_delay_duration must be 2, 4 or 6")
955+
q = [("broadcaster_id", broadcaster_id), ("moderator_id", moderator_id)]
956+
data = {
957+
"emote_mode": emote_mode,
958+
"follower_mode": follower_mode,
959+
"follower_mode_duration": follower_mode_duration,
960+
"slow_mode": slow_mode,
961+
"slow_mode_wait_time": slow_mode_wait_time,
962+
"subscriber_mode": subscriber_mode,
963+
"unique_chat_mode": unique_chat_mode,
964+
"non_moderator_chat_delay": non_moderator_chat_delay,
965+
"non_moderator_chat_delay_duration": non_moderator_chat_delay_duration,
966+
}
967+
data = {k: v for k, v in data.items() if v is not None}
968+
return await self.request(Route("PATCH", "chat/settings", query=q, body=data, token=token))

twitchio/models.py

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
"ChannelInfo",
7171
"Poll",
7272
"PollChoice",
73+
"Goal",
74+
"ChatSettings",
7375
)
7476

7577

@@ -1375,9 +1377,9 @@ class Poll:
13751377
duration: :class:`int`
13761378
Total duration for the poll (in seconds).
13771379
started_at: :class:`datetime.datetime`
1378-
Date and time the the poll was started.
1380+
Date and time the poll was started.
13791381
ended_at: :class:`datetime.datetime`
1380-
Date and time the the poll was ended.
1382+
Date and time the poll was ended.
13811383
"""
13821384

13831385
__slots__ = (
@@ -1441,3 +1443,111 @@ def __init__(self, data: dict):
14411443

14421444
def __repr__(self):
14431445
return f"<PollChoice id={self.id} title={self.title} votes={self.votes} channel_points_votes={self.channel_points_votes} bits_votes={self.bits_votes}>"
1446+
1447+
class Goal:
1448+
"""
1449+
Represents a list of Goals for a broadcaster / channel
1450+
1451+
Attributes
1452+
-----------
1453+
id: :class:`str`
1454+
An ID that uniquely identifies this goal.
1455+
broadcaster: :class:`~twitchio.PartialUser`
1456+
User of the broadcaster.
1457+
type: :class:`str`
1458+
The type of goal.
1459+
Valid values: follower, subscription and new_subscription.
1460+
description: :class:`str`
1461+
A description of the goal, if specified.
1462+
current_amount: :class:`int`
1463+
The current value.
1464+
target_amount: :class:`int`
1465+
Number of Bits required to vote once with Bits.
1466+
created_at: :class:`datetime.datetime`
1467+
Date and time of when the broadcaster created the goal.
1468+
"""
1469+
1470+
__slots__ = (
1471+
"id",
1472+
"broadcaster",
1473+
"type",
1474+
"description",
1475+
"current_amount",
1476+
"target_amount",
1477+
"created_at",
1478+
)
1479+
1480+
def __init__(self, http: "TwitchHTTP", data: dict):
1481+
self.id: str = data["id"]
1482+
self.broadcaster = PartialUser(http, data["broadcaster_id"], data["broadcaster_login"])
1483+
self.type: str = data["type"]
1484+
self.description: str = data["description"]
1485+
self.current_amount: int = data["current_amount"]
1486+
self.target_amount: int = data["target_amount"]
1487+
self.created_at: datetime.datetime = parse_timestamp(data["created_at"])
1488+
1489+
def __repr__(self):
1490+
return f"<Goal id={self.id} broadcaster={self.broadcaster} description={self.description} current_amount={self.current_amount} target_amount={self.target_amount} created_at={self.created_at}>"
1491+
1492+
class ChatSettings:
1493+
"""
1494+
Represents current chat settings of a broadcaster / channel
1495+
1496+
Attributes
1497+
-----------
1498+
broadcaster: :class:`~twitchio.PartialUser`
1499+
User of the broadcaster. Only returns the ID.
1500+
emote_mode: :class:`bool`
1501+
Indicates whether emote only mode is enabled.
1502+
follower_mode: :class:`bool`
1503+
Indicates whether follower only chat is enabled.
1504+
follower_mode_duration: Optional[:class:`int`]
1505+
The length of time, in minutes, that the followers must have followed the broadcaster to participate in chat.
1506+
slow_mode: :class:`bool`
1507+
Indicates whether the chat is in slow mode.
1508+
slow_mode_wait_time: Optional[:class:`int`]
1509+
The amount of time, in seconds, that users need to wait between sending messages.
1510+
subscriber_mode: :class:`bool`
1511+
Indicates whether only users that subscribe to the broadcaster's channel can talk in chat.
1512+
unique_chat_mode: :class:`bool`
1513+
Indicates whether the broadcaster requires users to post only unique messages in the chat room.
1514+
moderator: Optional[:class:`~twitchio.PartialUser`]
1515+
The User of the moderator, if provided. Only returns the ID.
1516+
non_moderator_chat_delay: Optional[:class:`bool`]
1517+
Indicates whether the broadcaster adds a short delay before chat messages appear in the chat room.
1518+
non_moderator_chat_delay_duration: Optional[:class:`int`]
1519+
The amount of time, in seconds, that messages are delayed from appearing in chat.
1520+
"""
1521+
1522+
__slots__ = (
1523+
"broadcaster",
1524+
"emote_mode",
1525+
"follower_mode",
1526+
"follower_mode_duration",
1527+
"slow_mode",
1528+
"slow_mode_wait_time",
1529+
"subscriber_mode",
1530+
"unique_chat_mode",
1531+
"moderator",
1532+
"non_moderator_chat_delay",
1533+
"non_moderator_chat_delay_duration",
1534+
)
1535+
1536+
def __init__(self, http: "TwitchHTTP", data: dict):
1537+
self.broadcaster = PartialUser(http, data["broadcaster_id"], None)
1538+
self.emote_mode: bool = data["emote_mode"]
1539+
self.follower_mode: bool = data["follower_mode"]
1540+
self.follower_mode_duration: Optional[int] = data.get("follower_mode_duration")
1541+
self.slow_mode: bool = data["slow_mode"]
1542+
self.slow_mode_wait_time: Optional[int] = data.get("slow_mode_wait_time")
1543+
self.subscriber_mode: bool = data["subscriber_mode"]
1544+
self.unique_chat_mode: bool = data["unique_chat_mode"]
1545+
self.non_moderator_chat_delay: Optional[bool] = data.get("non_moderator_chat_delay")
1546+
self.non_moderator_chat_delay_duration: Optional[int] = data.get("non_moderator_chat_delay_duration")
1547+
try:
1548+
self.moderator = PartialUser(http, data["moderator_id"], None)
1549+
except KeyError:
1550+
self.moderator = None
1551+
1552+
def __repr__(self):
1553+
return f"<ChatSettings broadcaster={self.broadcaster} emote_mode={self.emote_mode} follower_mode={self.follower_mode} slow_mode={self.slow_mode} subscriber_mode={self.subscriber_mode} unique_chat_mode={self.unique_chat_mode}>"

twitchio/user.py

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ async def fetch_channel_teams(self):
801801
broadcaster_id=str(self.id),
802802
)
803803

804-
return [ChannelTeams(self._http, x) for x in data]
804+
return [ChannelTeams(self._http, x) for x in data["data"]] if data["data"] else []
805805

806806
async def fetch_polls(self, token: str, poll_ids: Optional[List[str]] = None, first: Optional[int] = 20):
807807
"""|coro|
@@ -824,7 +824,7 @@ async def fetch_polls(self, token: str, poll_ids: Optional[List[str]] = None, fi
824824
from .models import Poll
825825

826826
data = await self._http.get_polls(broadcaster_id=str(self.id), token=token, poll_ids=poll_ids, first=first)
827-
return [Poll(self._http, x) for x in data["data"]] if data["data"] else None
827+
return [Poll(self._http, x) for x in data["data"]] if data["data"] else []
828828

829829
async def create_poll(
830830
self,
@@ -904,6 +904,111 @@ async def end_poll(self, token: str, poll_id: str, status: str):
904904
data = await self._http.patch_poll(broadcaster_id=str(self.id), token=token, id=poll_id, status=status)
905905
return Poll(self._http, data[0])
906906

907+
async def fetch_goals(self, token: str):
908+
"""|coro|
909+
910+
Fetches a list of goals for the specified channel/broadcaster.
911+
912+
Parameters
913+
-----------
914+
token: :class:`str`
915+
An oauth token with the user:read:broadcast scope
916+
917+
Returns
918+
--------
919+
List[:class:`twitchio.Goal`]
920+
"""
921+
from .models import Goal
922+
923+
data = await self._http.get_goals(broadcaster_id=str(self.id), token=token)
924+
return [Goal(self._http, x) for x in data]
925+
926+
async def fetch_chat_settings(self, moderator_id: Optional[int] = None, token: Optional[str] = None):
927+
"""|coro|
928+
929+
Fetches the current chat settings for this channel/broadcaster.
930+
931+
Parameters
932+
-----------
933+
moderator_id: Optional[:class:`int`]
934+
The ID of a user that has permission to moderate the broadcaster's chat room.
935+
token: Optional[:class:`str`]
936+
An oauth token with the moderator:read:chat_settings scope. Required if moderator_id is provided.
937+
938+
Returns
939+
--------
940+
:class:`twitchio.ChatSettings`
941+
"""
942+
from .models import ChatSettings
943+
944+
data = await self._http.get_chat_settings(broadcaster_id=str(self.id), moderator_id=str(moderator_id), token=token)
945+
return ChatSettings(self._http, data[0])
946+
947+
async def update_chat_settings(
948+
self,
949+
moderator_id: int,
950+
token: str,
951+
emote_mode: Optional[bool] = None,
952+
follower_mode: Optional[bool] = None,
953+
follower_mode_duration: Optional[int] = None,
954+
slow_mode: Optional[bool] = None,
955+
slow_mode_wait_time: Optional[int] = None,
956+
subscriber_mode: Optional[bool] = None,
957+
unique_chat_mode: Optional[bool] = None,
958+
non_moderator_chat_delay: Optional[bool] = None,
959+
non_moderator_chat_delay_duration: Optional[int] = None
960+
):
961+
"""|coro|
962+
963+
Updates the current chat settings for this channel/broadcaster.
964+
965+
Parameters
966+
-----------
967+
moderator_id: :class:`int`
968+
The ID of a user that has permission to moderate the broadcaster's chat room.
969+
token: :class:`str`
970+
An oauth token with the moderator:manage:chat_settings scope.
971+
emote_mode: Optional[:class:`bool`]
972+
A boolean to determine whether chat must contain only emotes or not.
973+
follower_mode: Optional[:class:`bool`]
974+
A boolean to determine whether chat must contain only emotes or not.
975+
follower_mode_duration: Optional[:class:`int`]
976+
The length of time, in minutes, that the followers must have followed the broadcaster to participate in chat.
977+
Values: 0 (no restriction) through 129600 (3 months). The default is 0.
978+
slow_mode: Optional[:class:`bool`]
979+
A boolean to determine whether the broadcaster limits how often users in the chat room are allowed to send messages.
980+
slow_mode_wait_time: Optional[:class:`int`]
981+
The amount of time, in seconds, that users need to wait between sending messages.
982+
Values: 3 through 120 (2 minute delay). The default is 30 seconds.
983+
subscriber_mode: Optional[:class:`bool`]
984+
A boolean to determine whether only users that subscribe to the broadcaster's channel can talk in chat.
985+
unique_chat_mode: Optional[:class:`bool`]
986+
A boolean to determine whether the broadcaster requires users to post only unique messages in chat.
987+
non_moderator_chat_delay: Optional[:class:`bool`]
988+
A boolean to determine whether the broadcaster adds a short delay before chat messages appear in chat.
989+
non_moderator_chat_delay_duration: Optional[:class:`int`]
990+
The amount of time, in seconds, that messages are delayed from appearing in chat.
991+
Valid values: 2, 4 and 6.
992+
993+
Returns
994+
--------
995+
:class:`twitchio.ChatSettings`
996+
"""
997+
from .models import ChatSettings
998+
data = await self._http.patch_chat_settings(
999+
broadcaster_id=str(self.id),
1000+
moderator_id=str(moderator_id),
1001+
token=token, emote_mode=emote_mode,
1002+
follower_mode=follower_mode,
1003+
follower_mode_duration=follower_mode_duration,
1004+
slow_mode=slow_mode,
1005+
slow_mode_wait_time=slow_mode_wait_time,
1006+
subscriber_mode=subscriber_mode,
1007+
unique_chat_mode=unique_chat_mode,
1008+
non_moderator_chat_delay=non_moderator_chat_delay,
1009+
non_moderator_chat_delay_duration=non_moderator_chat_delay_duration
1010+
)
1011+
return ChatSettings(self._http, data[0])
9071012

9081013
class BitLeaderboardUser(PartialUser):
9091014

0 commit comments

Comments
 (0)