Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into fix/routines
Browse files Browse the repository at this point in the history
Updater with latest changes from master.
  • Loading branch information
EvieePy committed Mar 29, 2024
2 parents c79c225 + 8a2a839 commit bd81427
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 52 deletions.
18 changes: 0 additions & 18 deletions .github/PULL_REQUEST_TEMPLATE.md

This file was deleted.

15 changes: 15 additions & 0 deletions .github/workflows/signoff.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: validate-signoff
on:
pull_request:
types:
- opened
- edited

jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: PR Description Check
uses: pythonistaguild/pr-description-check@v1.0
with:
content: "[x] I have read and agree to the [Developer Certificate of Origin]"
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Thank you to all those who contribute and help TwitchIO grow.

Special thanks to:

`SnowyLuma <https://github.com/SnowyLuma>`_
`LostLuma (Lilly) <https://github.com/LostLuma>`_

`Harmon <https://github.com/Harmon758>`_

Expand Down
37 changes: 37 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
:orphan:

2.9.2
=======
- TwitchIO
- Changes:
- :func:`PartialUser.fetch_moderated_channels <twitchio.PartialUser.fetch_moderated_channels>` returns "broadcaster_login" api field instead of "broadcaster_name"

- Bug fixes
- fix: :func:`PartialUser.fetch_moderated_channels <twitchio.PartialUser.fetch_moderated_channels>` used "user_" prefix from payload, now uses "broadcaster_" instead


2.9.1
=======
- ext.eventsub
- Bug fixes
- fix: Special-cased a restart when a specific known bad frame is received.


2.9.0
=======
- TwitchIO
- Additions
- Added :class:`~twitchio.AdSchedule` and :class:`~twitchio.Emote`
- Added the new ad-related methods for :class:`~twitchio.PartialUser`:
- :func:`~twitchio.PartialUser.fetch_ad_schedule`
- :func:`~twitchio.PartialUser.snooze_ad`
- Added new method :func:`~twitchio.PartialUser.fetch_user_emotes` to :class:`~twitchio.PartialUser`
- Added :func:`~twitchio.PartialUser.fetch_moderated_channels` to :class:`~twitchio.PartialUser`

- Bug fixes
- Fixed ``event_token_expired`` not applying to the current request.

- ext.eventsub
- Bug fixes
- Fixed a crash where a Future could be None, causing unintentional errors.
- Special-cased a restart when a specific known bad frame is received.


2.8.2
======
- ext.commands
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

on_rtd = os.environ.get("READTHEDOCS") == "True"
project = "TwitchIO"
copyright = "2023, TwitchIO"
copyright = "2024, TwitchIO"
author = "PythonistaGuild"

# The full version, including alpha/beta/rc tags
Expand Down
2 changes: 1 addition & 1 deletion docs/exts/eventsub.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ This is a list of events dispatched by the eventsub ext.

Called when someone cheers on a channel you've subscribed to.

.. function:: event_eventsub_notification_raid(event: Channel)
.. function:: event_eventsub_notification_raid(event: ChannelRaidData)

Called when someone raids a channel you've subscribed to.

Expand Down
14 changes: 14 additions & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ ActiveExtension
:members:
:inherited-members:

AdSchedule
------------
.. attributetable:: AdSchedule

.. autoclass:: AdSchedule
:members:

AutomodCheckMessage
---------------------
.. attributetable:: AutomodCheckMessage
Expand Down Expand Up @@ -213,6 +220,13 @@ CustomRewardRedemption
:members:
:inherited-members:

Emote
------
.. attributetable:: Emote

.. autoclass:: Emote
:members:

Extension
-----------
.. attributetable:: Extension
Expand Down
4 changes: 2 additions & 2 deletions twitchio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
__title__ = "TwitchIO"
__author__ = "TwitchIO, PythonistaGuild"
__license__ = "MIT"
__copyright__ = "Copyright 2017-2022 (c) TwitchIO"
__version__ = "2.8.2"
__copyright__ = "Copyright 2017-present (c) TwitchIO"
__version__ = "2.9.1"

from .client import Client
from .user import *
Expand Down
29 changes: 21 additions & 8 deletions twitchio/ext/eventsub/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from twitchio import PartialUser, Unauthorized, HTTPException

if TYPE_CHECKING:
from typing_extensions import Literal
from twitchio import Client

logger = logging.getLogger("twitchio.ext.eventsub.ws")
Expand All @@ -32,7 +33,7 @@ def __init__(self, event_type: Tuple[str, int, Type[models.EventData]], conditio
self.token = token
self.subscription_id: Optional[str] = None
self.cost: Optional[int] = None
self.created: asyncio.Future[Tuple[bool, int]] | None = asyncio.Future()
self.created: asyncio.Future[Tuple[Literal[False], int] | Tuple[Literal[True], None]] | None = asyncio.Future()


_T = TypeVar("_T")
Expand Down Expand Up @@ -117,18 +118,25 @@ async def _subscribe(self, obj: _Subscription) -> dict | None:
try:
resp = await self._http.create_websocket_subscription(obj.event, obj.condition, self._session_id, obj.token)
except HTTPException as e:
assert obj.created
obj.created.set_result((False, e.status)) # type: ignore
if obj.created:
obj.created.set_result((False, e.status))

else:
logger.error(
"An error (%s %s) occurred while attempting to resubscribe to an event on reconnect: %s",
e.status,
e.reason,
e.message,
)

return None

else:
assert obj.created
obj.created.set_result((True, None)) # type: ignore
if obj.created:
obj.created.set_result((True, None))

data = resp["data"][0]
cost = data["cost"]
self.remaining_slots = resp["max_total_cost"] - resp["total_cost"]
obj.cost = cost
obj.cost = data["cost"]

return data

Expand Down Expand Up @@ -204,6 +212,11 @@ async def pump(self) -> None:
except TypeError as e:
logger.warning(f"Received bad frame: {e.args[0]}")

if "257" in e.args[0]: # websocket was closed, reconnect
logger.info("Known bad frame, restarting connection")
await self.connect()
return

except Exception as e:
logger.error("Exception in the pump function!", exc_info=e)
raise
Expand Down
62 changes: 43 additions & 19 deletions twitchio/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,22 +121,8 @@ async def request(self, route: Route, *, paginate=True, limit=100, full_body=Fal
raise errors.NoClientID("A Client ID is required to use the Twitch API")
headers = route.headers or {}

if force_app_token and "Authorization" not in headers:
if not self.client_secret:
raise errors.NoToken(
"An app access token is required for this route, please provide a client id and client secret"
)
if self.app_token is None:
await self._generate_login()
headers["Authorization"] = f"Bearer {self.app_token}"
elif not self.token and not self.client_secret and "Authorization" not in headers:
raise errors.NoToken(
"Authorization is required to use the Twitch API. Pass token and/or client_secret to the Client constructor"
)
if "Authorization" not in headers:
if not self.token:
await self._generate_login()
headers["Authorization"] = f"Bearer {self.token}"
await self._apply_auth(headers, force_app_token, False)

headers["Client-ID"] = self.client_id

if not self.session:
Expand Down Expand Up @@ -165,7 +151,7 @@ def get_limit():
q = [("after", cursor), *q]
q = [("first", get_limit()), *q]
path = path.with_query(q)
body, is_text = await self._request(route, path, headers)
body, is_text = await self._request(route, path, headers, force_app_token=force_app_token)
if is_text:
return body
if full_body:
Expand All @@ -182,7 +168,26 @@ def get_limit():
is_finished = reached_limit() if limit is not None else True if paginate else True
return data

async def _request(self, route, path, headers, utilize_bucket=True):
async def _apply_auth(self, headers: dict, force_app_token: bool, force_apply: bool) -> None:
if force_app_token and "Authorization" not in headers:
if not self.client_secret:
raise errors.NoToken(
"An app access token is required for this route, please provide a client id and client secret"
)
if self.app_token is None:
await self._generate_login()
headers["Authorization"] = f"Bearer {self.app_token}"
elif not self.token and not self.client_secret and "Authorization" not in headers:
raise errors.NoToken(
"Authorization is required to use the Twitch API. Pass token and/or client_secret to the Client constructor"
)
if "Authorization" not in headers or force_apply:
if not self.token:
await self._generate_login()

headers["Authorization"] = f"Bearer {self.token}"

async def _request(self, route, path, headers, utilize_bucket=True, force_app_token: bool = False):
reason = None

for attempt in range(5):
Expand Down Expand Up @@ -224,6 +229,7 @@ async def _request(self, route, path, headers, utilize_bucket=True):
if "Invalid OAuth token" in message_json.get("message", ""):
try:
await self._generate_login()
await self._apply_auth(headers, force_app_token, True)
continue
except:
raise errors.Unauthorized(
Expand Down Expand Up @@ -645,7 +651,6 @@ async def get_hype_train(self, broadcaster_id: str, id: Optional[str] = None, to
)

async def post_automod_check(self, token: str, broadcaster_id: str, *msgs: List[Dict[str, str]]):
print(msgs)
return await self.request(
Route(
"POST",
Expand All @@ -656,6 +661,14 @@ async def post_automod_check(self, token: str, broadcaster_id: str, *msgs: List[
)
)

async def post_snooze_ad(self, token: str, broadcaster_id: str):
q = [("broadcaster_id", broadcaster_id)]
return await self.request(Route("POST", "channels/ads/schedule/snooze", query=q, token=token))

async def get_ad_schedule(self, token: str, broadcaster_id: str):
q = [("broadcaster_id", broadcaster_id)]
return await self.request(Route("GET", "channels/ads", query=q, token=token))

async def get_channel_ban_unban_events(self, token: str, broadcaster_id: str, user_ids: List[str] = None):
q = [("broadcaster_id", broadcaster_id)]
if user_ids:
Expand All @@ -668,6 +681,10 @@ async def get_channel_bans(self, token: str, broadcaster_id: str, user_ids: List
q.extend(("user_id", id) for id in user_ids)
return await self.request(Route("GET", "moderation/banned", query=q, token=token))

async def get_moderated_channels(self, token: str, user_id: str):
q = [("user_id", user_id)]
return await self.request(Route("GET", "moderation/channels", query=q, token=token))

async def get_channel_moderators(self, token: str, broadcaster_id: str, user_ids: List[str] = None):
q = [("broadcaster_id", broadcaster_id)]
if user_ids:
Expand All @@ -688,6 +705,13 @@ async def get_search_channels(self, query: str, token: str = None, live: bool =
Route("GET", "search/channels", query=[("query", query), ("live_only", str(live))], token=token)
)

async def get_user_emotes(self, user_id: str, broadcaster_id: Optional[str], token: str):
q: List = [("user_id", user_id)]
if broadcaster_id:
q.append(("broadcaster_id", broadcaster_id))

return await self.request(Route("GET", "chat/emotes/user", query=q, token=token))

async def get_stream_key(self, token: str, broadcaster_id: str):
return await self.request(
Route("GET", "streams/key", query=[("broadcaster_id", broadcaster_id)], token=token), paginate=False
Expand Down
Loading

0 comments on commit bd81427

Please sign in to comment.